fix(stream_chat_persistence): tests
This commit is contained in:
@@ -20,7 +20,7 @@ jobs:
|
||||
repo: GetStream/flutter-samples
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
dispatch_stable:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: benc-uk/workflow-dispatch@v1
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fast fail the script on failures.
|
||||
set -e
|
||||
|
||||
pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '.g.dart$'
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
@@ -32,6 +33,7 @@ jobs:
|
||||
melos exec -c 3 --ignore="*example*" -- \
|
||||
tuneup check
|
||||
- name: 'Pub Check'
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
melos exec -c 1 --no-private --ignore="*example*" -- \
|
||||
pub publish --dry-run
|
||||
@@ -68,27 +70,31 @@ jobs:
|
||||
run: |
|
||||
./.github/workflows/scripts/install-tools.sh
|
||||
flutter pub global activate coverage
|
||||
flutter pub global activate remove_from_coverage
|
||||
- name: 'Bootstrap Workspace'
|
||||
run: melos bootstrap
|
||||
- name: 'Dart Test'
|
||||
run: |
|
||||
cd packages/stream_chat
|
||||
flutter pub run test --coverage coverage/
|
||||
format_coverage --lcov --in=coverage/ --out=lcov.info --packages=.packages --report-on=lib
|
||||
format_coverage --lcov --in=coverage/ --out=coverage/lcov.info --packages=.packages --report-on=lib
|
||||
- name: 'Flutter Test'
|
||||
run: |
|
||||
melos exec -c 3 --flutter --dir-exists=test --ignore="*example*" --ignore="*web*" -- \
|
||||
flutter test --coverage
|
||||
- name: CodeCov
|
||||
run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
melos exec -c 3 --fail-fast --dir-exists=test --ignore="*example*" --ignore="*web*" -- \
|
||||
"\$MELOS_ROOT_PATH/.github/workflows/scripts/coverage.sh"
|
||||
bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
|
||||
- uses: VeryGoodOpenSource/[email protected]
|
||||
with:
|
||||
path: packages/stream_chat/lcov.info
|
||||
min_coverage: 50
|
||||
path: packages/stream_chat/coverage/lcov.info
|
||||
min_coverage: 40
|
||||
- uses: VeryGoodOpenSource/[email protected]
|
||||
with:
|
||||
path: packages/stream_chat_persistence/coverage/lcov.info
|
||||
min_coverage: 0.2
|
||||
min_coverage: 95
|
||||
- uses: VeryGoodOpenSource/[email protected]
|
||||
with:
|
||||
path: packages/stream_chat_flutter_core/coverage/lcov.info
|
||||
|
||||
@@ -758,7 +758,8 @@ class StreamChatClient {
|
||||
await _chatPersistenceClient?.updateChannelQueries(
|
||||
filter,
|
||||
channels.map((c) => c.channel.cid).toList(),
|
||||
paginationParams?.offset == null || paginationParams.offset == 0,
|
||||
clearQueryCache:
|
||||
paginationParams?.offset == null || paginationParams.offset == 0,
|
||||
);
|
||||
|
||||
state.channels = updateData.key;
|
||||
|
||||
@@ -100,10 +100,9 @@ abstract class ChatPersistenceClient {
|
||||
/// the list of matching rows will be deleted
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
bool clearQueryCache,
|
||||
);
|
||||
List<String> cids, {
|
||||
bool clearQueryCache = false,
|
||||
});
|
||||
|
||||
/// Remove a message by [messageId]
|
||||
Future<void> deleteMessageById(String messageId) =>
|
||||
|
||||
@@ -121,6 +121,46 @@ class Event {
|
||||
_$EventToJson(this),
|
||||
topLevelFields,
|
||||
);
|
||||
|
||||
/// Creates a copy of [Event] with specified attributes overridden.
|
||||
Event copyWith({
|
||||
String type,
|
||||
String cid,
|
||||
String channelId,
|
||||
String channelType,
|
||||
String connectionId,
|
||||
DateTime createdAt,
|
||||
OwnUser me,
|
||||
User user,
|
||||
Message message,
|
||||
EventChannel channel,
|
||||
Member member,
|
||||
Reaction reaction,
|
||||
int totalUnreadCount,
|
||||
int unreadChannels,
|
||||
bool online,
|
||||
String parentId,
|
||||
Map<String, dynamic> extraData,
|
||||
}) =>
|
||||
Event(
|
||||
type: type ?? this.type,
|
||||
cid: cid ?? this.cid,
|
||||
connectionId: connectionId ?? this.connectionId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
me: me ?? this.me,
|
||||
user: user ?? this.user,
|
||||
message: message ?? this.message,
|
||||
totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount,
|
||||
unreadChannels: unreadChannels ?? this.unreadChannels,
|
||||
reaction: reaction ?? this.reaction,
|
||||
online: online ?? this.online,
|
||||
channel: channel ?? this.channel,
|
||||
member: member ?? this.member,
|
||||
channelId: channelId ?? this.channelId,
|
||||
channelType: channelType ?? this.channelType,
|
||||
parentId: parentId ?? this.parentId,
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
}
|
||||
|
||||
/// The channel embedded in the event object
|
||||
|
||||
@@ -27,4 +27,16 @@ class Read {
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$ReadToJson(this);
|
||||
|
||||
/// Creates a copy of [Read] with specified attributes overridden.
|
||||
Read copyWith({
|
||||
DateTime lastRead,
|
||||
User user,
|
||||
int unreadMessages,
|
||||
}) =>
|
||||
Read(
|
||||
lastRead: lastRead ?? this.lastRead,
|
||||
user: user ?? this.user,
|
||||
unreadMessages: unreadMessages ?? this.unreadMessages,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,4 +100,28 @@ class User {
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() =>
|
||||
Serialization.moveFromExtraDataToRoot(_$UserToJson(this), topLevelFields);
|
||||
|
||||
/// Creates a copy of [User] with specified attributes overridden.
|
||||
User copyWith({
|
||||
String id,
|
||||
String role,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime lastActive,
|
||||
bool online,
|
||||
Map<String, dynamic> extraData,
|
||||
bool banned,
|
||||
List<String> teams,
|
||||
}) =>
|
||||
User(
|
||||
id: id ?? this.id,
|
||||
role: role ?? this.role,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastActive: lastActive ?? this.lastActive,
|
||||
online: online ?? this.online,
|
||||
extraData: extraData ?? this.extraData,
|
||||
banned: banned ?? this.banned,
|
||||
teams: teams ?? this.teams,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,14 +170,14 @@ class ChannelPreview extends StatelessWidget {
|
||||
|
||||
if (lastMessageAt.millisecondsSinceEpoch >=
|
||||
startOfDay.millisecondsSinceEpoch) {
|
||||
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
||||
stringDate = Jiffy(lastMessageAt.toLocal()).jm;
|
||||
} else if (lastMessageAt.millisecondsSinceEpoch >=
|
||||
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
|
||||
stringDate = 'Yesterday';
|
||||
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
|
||||
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
|
||||
} else {
|
||||
stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy');
|
||||
stringDate = Jiffy(lastMessageAt.toLocal()).yMd;
|
||||
}
|
||||
|
||||
return Text(
|
||||
|
||||
@@ -28,14 +28,14 @@ class DateDivider extends StatelessWidget {
|
||||
now.subtract(Duration(days: 7)),
|
||||
Units.DAY,
|
||||
)) {
|
||||
dayInfo = createdAt.format('EEEE');
|
||||
dayInfo = createdAt.EEEE;
|
||||
} else if (Jiffy(createdAt).isAfter(
|
||||
Jiffy(now).subtract(years: 1),
|
||||
Units.DAY,
|
||||
)) {
|
||||
dayInfo = createdAt.format('MMMM d');
|
||||
dayInfo = createdAt.MMMd;
|
||||
} else {
|
||||
dayInfo = createdAt.format('MMMM d');
|
||||
dayInfo = createdAt.MMMd;
|
||||
}
|
||||
|
||||
if (uppercase) dayInfo = dayInfo.toUpperCase();
|
||||
|
||||
@@ -220,7 +220,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
24) {
|
||||
return 'yesterday';
|
||||
} else {
|
||||
return 'on ${Jiffy(dateTime).format("MMM do")}';
|
||||
return 'on ${Jiffy(dateTime).MMMd}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2233,7 +2233,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
void _parseExistingMessage(Message message) {
|
||||
textEditingController.text = message.text;
|
||||
_messageIsPresent = true;
|
||||
for (final attachment in message.attachments) {
|
||||
for (final attachment in message?.attachments) {
|
||||
_attachments[attachment.id] = attachment.copyWith(
|
||||
uploadState: attachment.uploadState ?? UploadState.success(),
|
||||
);
|
||||
|
||||
@@ -84,9 +84,9 @@ class MessageSearchItem extends StatelessWidget {
|
||||
if (now.year != createdAt.year ||
|
||||
now.month != createdAt.month ||
|
||||
now.day != createdAt.day) {
|
||||
stringDate = Jiffy(createdAt.toLocal()).format('dd/MM/yyyy');
|
||||
stringDate = Jiffy(createdAt.toLocal()).yMd;
|
||||
} else {
|
||||
stringDate = Jiffy(createdAt.toLocal()).format('HH:mm');
|
||||
stringDate = Jiffy(createdAt.toLocal()).jm;
|
||||
}
|
||||
|
||||
return Text(
|
||||
|
||||
@@ -3,9 +3,11 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
/// Widget used to provide information about the chat to the widget tree
|
||||
///
|
||||
@@ -121,4 +123,11 @@ class StreamChatState extends State<StreamChat> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final locale = ui.window.locale;
|
||||
Jiffy.locale(locale.languageCode);
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,9 @@ dependencies:
|
||||
lottie: ^1.0.0
|
||||
substring_highlight: ^0.1.2
|
||||
flutter_slidable: ^0.5.7
|
||||
clipboard: ^0.1.2+8
|
||||
image_gallery_saver: ^1.6.7
|
||||
share_plus: ^2.0.0
|
||||
photo_manager: ^1.0.0
|
||||
transparent_image: ^1.0.0
|
||||
ezanimation: ^0.4.1
|
||||
synchronized: ^3.0.0
|
||||
characters: ^1.0.0
|
||||
|
||||
@@ -69,7 +69,7 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(find.text('22/06/2020'), findsOneWidget);
|
||||
expect(find.text('6/22/2020'), findsOneWidget);
|
||||
expect(find.text('test name'), findsOneWidget);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.text('hello'), findsOneWidget);
|
||||
|
||||
@@ -19,10 +19,10 @@ class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
(select(channels)..where((c) => c.cid.equals(cid))).join([
|
||||
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
||||
]).map((rows) {
|
||||
final channel = rows.readTable(channels);
|
||||
final createdBy = rows.readTable(users);
|
||||
final channel = rows.readTableOrNull(channels);
|
||||
final createdBy = rows.readTableOrNull(users);
|
||||
return channel.toChannelModel(createdBy: createdBy?.toUser());
|
||||
}).getSingle();
|
||||
}).getSingleOrNull();
|
||||
|
||||
/// Delete all channels by matching cid in [cids]
|
||||
///
|
||||
|
||||
@@ -32,7 +32,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids, {
|
||||
bool clearQueryCache,
|
||||
bool clearQueryCache = false,
|
||||
}) async =>
|
||||
transaction(() async {
|
||||
final hash = _computeHash(filter);
|
||||
@@ -57,6 +57,14 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
});
|
||||
});
|
||||
|
||||
///
|
||||
Future<List<String>> getCachedChannelCids(Map<String, dynamic> filter) {
|
||||
final hash = _computeHash(filter);
|
||||
return (select(channelQueries)..where((c) => c.queryHash.equals(hash)))
|
||||
.map((c) => c.channelCid)
|
||||
.get();
|
||||
}
|
||||
|
||||
/// Get list of channels by filter, sort and paginationParams
|
||||
Future<List<ChannelModel>> getChannels({
|
||||
Map<String, dynamic> filter,
|
||||
@@ -72,12 +80,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
return true;
|
||||
}(), '');
|
||||
|
||||
final hash = _computeHash(filter);
|
||||
final cachedChannelCids = await (select(channelQueries)
|
||||
..where((c) => c.queryHash.equals(hash)))
|
||||
.map((c) => c.channelCid)
|
||||
.get();
|
||||
|
||||
final cachedChannelCids = await getCachedChannelCids(filter);
|
||||
final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids));
|
||||
|
||||
final cachedChannels = await (query.join([
|
||||
|
||||
@@ -2,14 +2,13 @@ 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/connection_events.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
|
||||
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
|
||||
|
||||
part 'connection_event_dao.g.dart';
|
||||
|
||||
/// The Data Access Object for operations in [ConnectionEvents] table.
|
||||
@UseDao(tables: [ConnectionEvents, Users])
|
||||
@UseDao(tables: [ConnectionEvents])
|
||||
class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$ConnectionEventDaoMixin {
|
||||
/// Creates a new connection event dao instance
|
||||
@@ -18,16 +17,16 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
/// Get the latest stored connection event
|
||||
Future<Event> get connectionEvent => select(connectionEvents)
|
||||
.map((eventEntity) => eventEntity.toEvent())
|
||||
.getSingle();
|
||||
.getSingleOrNull();
|
||||
|
||||
/// Get the latest stored lastSyncAt
|
||||
Future<DateTime> get lastSyncAt =>
|
||||
select(connectionEvents).getSingle().then((r) => r?.lastSyncAt);
|
||||
select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt);
|
||||
|
||||
/// Update stored connection event with latest data
|
||||
Future<void> updateConnectionEvent(Event event) async =>
|
||||
transaction(() async {
|
||||
final connectionInfo = await select(connectionEvents).getSingle();
|
||||
final connectionInfo = await select(connectionEvents).getSingleOrNull();
|
||||
await into(connectionEvents).insert(
|
||||
ConnectionEventEntity(
|
||||
id: 1,
|
||||
|
||||
@@ -9,5 +9,4 @@ part of 'connection_event_dao.dart';
|
||||
mixin _$ConnectionEventDaoMixin on DatabaseAccessor<MoorChatDatabase> {
|
||||
$ConnectionEventsTable get connectionEvents =>
|
||||
attachedDatabase.connectionEvents;
|
||||
$UsersTable get users => attachedDatabase.users;
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
(delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
|
||||
|
||||
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
||||
final userEntity = rows.readTable(_users);
|
||||
final pinnedByEntity = rows.readTable(_pinnedByUsers);
|
||||
final msgEntity = rows.readTable(messages);
|
||||
final userEntity = rows.readTableOrNull(_users);
|
||||
final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers);
|
||||
final msgEntity = rows.readTableOrNull(messages);
|
||||
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id);
|
||||
final ownReactions = await _db.reactionDao.getReactionsByUserId(
|
||||
msgEntity.id,
|
||||
@@ -66,13 +66,13 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
])
|
||||
..where(messages.id.equals(id)))
|
||||
.map(_messageFromJoinRow)
|
||||
.getSingle();
|
||||
.getSingleOrNull();
|
||||
|
||||
/// Returns all the messages of a particular thread by matching
|
||||
/// [Messages.channelCid] with [cid]
|
||||
Future<List<Message>> getThreadMessages(String cid) async =>
|
||||
Future.wait(await (select(messages).join([
|
||||
leftOuterJoin(users, messages.userId.equalsExp(_users.id)),
|
||||
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||
leftOuterJoin(_pinnedByUsers,
|
||||
messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||
])
|
||||
@@ -89,18 +89,36 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
PaginationParams options,
|
||||
}) async {
|
||||
final msgList = await Future.wait(await (select(messages).join([
|
||||
innerJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||
innerJoin(
|
||||
leftOuterJoin(_users, messages.userId.equalsExp(_users.id)),
|
||||
leftOuterJoin(
|
||||
_pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||
])
|
||||
..where(isNotNull(messages.parentId))
|
||||
..where(messages.parentId.equals(parentId))
|
||||
..orderBy([OrderingTerm.asc(messages.createdAt)]))
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
|
||||
if (options?.lessThan != null && msgList.isNotEmpty) {
|
||||
final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan);
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
if (msgList.isNotEmpty) {
|
||||
if (options?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == options.lessThan,
|
||||
);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
}
|
||||
}
|
||||
if (options?.greaterThanOrEqual != null) {
|
||||
final greaterThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == options.greaterThanOrEqual,
|
||||
);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
}
|
||||
}
|
||||
if (options?.limit != null) {
|
||||
return msgList.take(options.limit).toList();
|
||||
}
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
@@ -123,24 +141,26 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
|
||||
if (messagePagination?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.lessThan,
|
||||
);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
if (msgList.isNotEmpty) {
|
||||
if (messagePagination?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.lessThan,
|
||||
);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messagePagination?.greaterThanOrEqual != null) {
|
||||
final greaterThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.greaterThanOrEqual,
|
||||
);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
if (messagePagination?.greaterThanOrEqual != null) {
|
||||
final greaterThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.greaterThanOrEqual,
|
||||
);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
}
|
||||
}
|
||||
if (messagePagination?.limit != null) {
|
||||
return msgList.take(messagePagination.limit).toList();
|
||||
}
|
||||
}
|
||||
if (messagePagination?.limit != null) {
|
||||
return msgList.take(messagePagination.limit).toList();
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
(delete(pinnedMessages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
|
||||
|
||||
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
||||
final userEntity = rows.readTable(users);
|
||||
final pinnedByEntity = rows.readTable(_pinnedByUsers);
|
||||
final msgEntity = rows.readTable(pinnedMessages);
|
||||
final userEntity = rows.readTableOrNull(users);
|
||||
final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers);
|
||||
final msgEntity = rows.readTableOrNull(pinnedMessages);
|
||||
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id);
|
||||
final ownReactions = await _db.reactionDao.getReactionsByUserId(
|
||||
msgEntity.id,
|
||||
@@ -66,7 +66,7 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
])
|
||||
..where(pinnedMessages.id.equals(id)))
|
||||
.map(_messageFromJoinRow)
|
||||
.getSingle();
|
||||
.getSingleOrNull();
|
||||
|
||||
/// Returns all the messages of a particular thread by matching
|
||||
/// [PinnedMessages.channelCid] with [cid]
|
||||
@@ -89,18 +89,36 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
PaginationParams options,
|
||||
}) async {
|
||||
final msgList = await Future.wait(await (select(pinnedMessages).join([
|
||||
innerJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
||||
innerJoin(_pinnedByUsers,
|
||||
leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)),
|
||||
leftOuterJoin(_pinnedByUsers,
|
||||
pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id)),
|
||||
])
|
||||
..where(isNotNull(pinnedMessages.parentId))
|
||||
..where(pinnedMessages.parentId.equals(parentId))
|
||||
..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]))
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
|
||||
if (options?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere((m) => m.id == options.lessThan);
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
if (msgList.isNotEmpty) {
|
||||
if (options?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == options.lessThan,
|
||||
);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
}
|
||||
}
|
||||
if (options?.greaterThanOrEqual != null) {
|
||||
final greaterThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == options.greaterThanOrEqual,
|
||||
);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
}
|
||||
}
|
||||
if (options?.limit != null) {
|
||||
return msgList.take(options.limit).toList();
|
||||
}
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
@@ -123,24 +141,26 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
|
||||
if (messagePagination?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.lessThan,
|
||||
);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
if (msgList.isNotEmpty) {
|
||||
if (messagePagination?.lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.lessThan,
|
||||
);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messagePagination?.greaterThanOrEqual != null) {
|
||||
final greaterThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.greaterThanOrEqual,
|
||||
);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
if (messagePagination?.greaterThanOrEqual != null) {
|
||||
final greaterThanIndex = msgList.indexWhere(
|
||||
(m) => m.id == messagePagination.greaterThanOrEqual,
|
||||
);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
}
|
||||
}
|
||||
if (messagePagination?.limit != null) {
|
||||
return msgList.take(messagePagination.limit).toList();
|
||||
}
|
||||
}
|
||||
if (messagePagination?.limit != null) {
|
||||
return msgList.take(messagePagination.limit).toList();
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
..where(reactions.messageId.equals(messageId))
|
||||
..orderBy([OrderingTerm.asc(reactions.createdAt)]))
|
||||
.map((rows) {
|
||||
final userEntity = rows.readTable(users);
|
||||
final reactionEntity = rows.readTable(reactions);
|
||||
final userEntity = rows.readTableOrNull(users);
|
||||
final reactionEntity = rows.readTableOrNull(reactions);
|
||||
return reactionEntity.toReaction(user: userEntity?.toUser());
|
||||
}).get();
|
||||
|
||||
|
||||
@@ -20,4 +20,10 @@ class UserDao extends DatabaseAccessor<MoorChatDatabase> with _$UserDaoMixin {
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
/// Returns the list of all the users stored in db
|
||||
Future<List<User>> getUsers() =>
|
||||
(select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)]))
|
||||
.map((it) => it.toUser())
|
||||
.get();
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
import 'package:stream_chat_persistence/src/converter/converter.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/shared/shared_db.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/entity.dart';
|
||||
|
||||
part 'moor_chat_database.g.dart';
|
||||
export 'shared/shared_db.dart';
|
||||
|
||||
LazyDatabase _openConnection(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
bool persistOnDisk = true,
|
||||
}) =>
|
||||
LazyDatabase(() async => SharedDB.constructDatabase(
|
||||
userId,
|
||||
logStatements: logStatements,
|
||||
persistOnDisk: persistOnDisk,
|
||||
));
|
||||
part 'moor_chat_database.g.dart';
|
||||
|
||||
/// A chat database implemented using moor
|
||||
@UseMoor(tables: [
|
||||
@@ -43,14 +36,9 @@ LazyDatabase _openConnection(
|
||||
class MoorChatDatabase extends _$MoorChatDatabase {
|
||||
/// Creates a new moor chat database instance
|
||||
MoorChatDatabase(
|
||||
this._userId, {
|
||||
logStatements = false,
|
||||
bool persistOnDisk = true,
|
||||
}) : super(_openConnection(
|
||||
_userId,
|
||||
logStatements: logStatements,
|
||||
persistOnDisk: persistOnDisk,
|
||||
));
|
||||
this._userId,
|
||||
QueryExecutor executor,
|
||||
) : super(executor);
|
||||
|
||||
/// Instantiate a new database instance
|
||||
MoorChatDatabase.connect(
|
||||
@@ -58,6 +46,10 @@ class MoorChatDatabase extends _$MoorChatDatabase {
|
||||
DatabaseConnection connection,
|
||||
) : super.connect(connection);
|
||||
|
||||
/// Custom constructor used only for testing
|
||||
@visibleForTesting
|
||||
MoorChatDatabase.testable(this._userId) : super(VmDatabase.memory());
|
||||
|
||||
final String _userId;
|
||||
|
||||
/// User id to which the database is connected
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
@@ -14,27 +15,49 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
|
||||
/// for native platform applications
|
||||
class SharedDB {
|
||||
/// Returns a new instance of [VmDatabase] created using [userId]
|
||||
/// on a regular isolate.
|
||||
///
|
||||
/// Generally used with [ConnectionMode.regular].
|
||||
static Future<VmDatabase> constructDatabase(
|
||||
/// Returns a new instance of [MoorChatDatabase].
|
||||
static MoorChatDatabase constructDatabase(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
bool persistOnDisk = true,
|
||||
}) async {
|
||||
ConnectionMode connectionMode = ConnectionMode.regular,
|
||||
}) {
|
||||
final dbName = 'db_$userId';
|
||||
if (persistOnDisk) {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = join(dir.path, '$dbName.sqlite');
|
||||
final file = File(path);
|
||||
return VmDatabase(file, logStatements: logStatements);
|
||||
}
|
||||
if (Platform.isMacOS || Platform.isLinux) {
|
||||
final file = File('$dbName.sqlite');
|
||||
return VmDatabase(file, logStatements: logStatements);
|
||||
}
|
||||
if (connectionMode == ConnectionMode.background) {
|
||||
return MoorChatDatabase.connect(
|
||||
userId,
|
||||
DatabaseConnection.delayed(Future(() async {
|
||||
final isolate = await _createMoorIsolate(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
);
|
||||
return isolate.connect();
|
||||
})),
|
||||
);
|
||||
}
|
||||
return MoorChatDatabase(
|
||||
userId,
|
||||
LazyDatabase(
|
||||
() async => _constructDatabase(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<VmDatabase> _constructDatabase(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = join(dir.path, '$dbName.sqlite');
|
||||
final file = File(path);
|
||||
return VmDatabase(file, logStatements: logStatements);
|
||||
}
|
||||
if (Platform.isMacOS || Platform.isLinux) {
|
||||
final file = File('$dbName.sqlite');
|
||||
return VmDatabase(file, logStatements: logStatements);
|
||||
}
|
||||
return VmDatabase.memory(logStatements: logStatements);
|
||||
}
|
||||
@@ -69,27 +92,6 @@ class SharedDB {
|
||||
|
||||
return await receivePort.first as MoorIsolate;
|
||||
}
|
||||
|
||||
/// Returns a new instance of [MoorChatDatabase] using the factory constructor
|
||||
/// [MoorChatDatabase.connect] created on a background isolate.
|
||||
///
|
||||
/// Generally used with [ConnectionMode.background].
|
||||
static MoorChatDatabase constructMoorChatDatabase(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
}) {
|
||||
final dbName = 'db_$userId';
|
||||
return MoorChatDatabase.connect(
|
||||
userId,
|
||||
DatabaseConnection.delayed(Future(() async {
|
||||
final isolate = await _createMoorIsolate(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
);
|
||||
return isolate.connect();
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _IsolateStartRequest {
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
import 'package:moor/backends.dart';
|
||||
// coverage:ignore-file
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
||||
|
||||
/// A Helper class to construct new instances of [MoorChatDatabase]
|
||||
class SharedDB {
|
||||
/// Returns a new instance of database.
|
||||
///
|
||||
/// Generally used with [ConnectionMode.regular].
|
||||
static Future<DelegatedDatabase> constructDatabase(
|
||||
/// Returns a new instance of [MoorChatDatabase].
|
||||
static MoorChatDatabase constructDatabase(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
bool persistOnDisk = true,
|
||||
ConnectionMode connectionMode = ConnectionMode.regular,
|
||||
}) {
|
||||
throw UnsupportedError(
|
||||
'No implementation of the constructDatabase api provided');
|
||||
}
|
||||
|
||||
/// Return a new instance of moor chat database.
|
||||
///
|
||||
/// Generally used with [ConnectionMode.background].
|
||||
static MoorChatDatabase constructMoorChatDatabase(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
}) {
|
||||
throw UnsupportedError(
|
||||
'No implementation of the constructMoorChatDatabase api provided');
|
||||
'No implementation of the constructDatabase api provided',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor_web.dart';
|
||||
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
||||
|
||||
@@ -6,27 +7,14 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
/// A Helper class to construct new instances of [MoorChatDatabase] specifically
|
||||
/// for Web applications
|
||||
class SharedDB {
|
||||
/// Returns a new instance of [WebDatabase] created using [userId].
|
||||
///
|
||||
/// Generally used with [ConnectionMode.regular].
|
||||
static Future<WebDatabase> constructDatabase(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
bool persistOnDisk = true, // ignored on web
|
||||
}) async {
|
||||
final dbName = 'db_$userId';
|
||||
return WebDatabase(dbName, logStatements: logStatements);
|
||||
}
|
||||
|
||||
/// Returns a new instance of [MoorChatDatabase] creating using the
|
||||
/// default constructor.
|
||||
///
|
||||
/// Generally used with [ConnectionMode.background].
|
||||
static MoorChatDatabase constructMoorChatDatabase(
|
||||
/// Returns a new instance of [MoorChatDatabase].
|
||||
static MoorChatDatabase constructDatabase(
|
||||
String userId, {
|
||||
bool logStatements = false,
|
||||
ConnectionMode connectionMode = ConnectionMode.regular, // Ignored on web
|
||||
}) {
|
||||
final dbName = 'db_$userId';
|
||||
return MoorChatDatabase(dbName, logStatements: logStatements);
|
||||
final queryExecutor = WebDatabase(dbName, logStatements: logStatements);
|
||||
return MoorChatDatabase(userId, queryExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
/// Represents a [ChannelQueries] table in [MoorChatDatabase].
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
/// Represents a [Members] table in [MoorChatDatabase].
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/list_converter.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
import 'package:stream_chat_persistence/src/entity/messages.dart';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
/// Represents a [Reads] table in [MoorChatDatabase].
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// coverage:ignore-file
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@ extension ChannelModelX on ChannelModel {
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: config.toJson(),
|
||||
config: config?.toJson(),
|
||||
frozen: frozen,
|
||||
lastMessageAt: lastMessageAt,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
deletedAt: deletedAt,
|
||||
memberCount: memberCount,
|
||||
createdById: createdBy.id,
|
||||
createdById: createdBy?.id,
|
||||
extraData: extraData,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:mutex/mutex.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/db/shared/shared_db.dart';
|
||||
|
||||
/// Various connection modes on which [StreamChatPersistenceClient] can work
|
||||
enum ConnectionMode {
|
||||
@@ -15,6 +14,9 @@ enum ConnectionMode {
|
||||
background,
|
||||
}
|
||||
|
||||
/// Signature for a function which provides instance of [MoorChatDatabase]
|
||||
typedef DatabaseProvider = MoorChatDatabase Function(String, ConnectionMode);
|
||||
|
||||
final _levelEmojiMapper = {
|
||||
Level.INFO: 'ℹ️',
|
||||
Level.WARNING: '⚠️',
|
||||
@@ -36,26 +38,6 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
||||
_logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler);
|
||||
}
|
||||
|
||||
/// A function that has a parameter of type [LogRecord].
|
||||
/// This is called on every new log record.
|
||||
/// By default the client will use the handler returned by
|
||||
/// [_getDefaultLogHandler].
|
||||
/// Setting it you can handle the log messages directly instead of have them
|
||||
/// written to stdout,
|
||||
/// this is very convenient if you use an error tracking tool or if you want
|
||||
/// to centralize your logs into one facility.
|
||||
///
|
||||
/// ```dart
|
||||
/// myLogHandlerFunction = (LogRecord record) {
|
||||
/// // do something with the record (ie. send it to Sentry or Fabric)
|
||||
/// }
|
||||
///
|
||||
/// final client = StreamChatPersistenceClient(
|
||||
/// logHandlerFunction: myLogHandlerFunction,
|
||||
/// );
|
||||
///```
|
||||
LogHandlerFunction logHandlerFunction;
|
||||
|
||||
/// [MoorChatDatabase] instance used by this client.
|
||||
@visibleForTesting
|
||||
MoorChatDatabase db;
|
||||
@@ -84,24 +66,25 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
||||
return ret;
|
||||
}
|
||||
|
||||
MoorChatDatabase _defaultDatabaseProvider(
|
||||
String userId,
|
||||
ConnectionMode mode,
|
||||
) =>
|
||||
SharedDB.constructDatabase(userId, connectionMode: mode);
|
||||
|
||||
@override
|
||||
Future<void> connect(String userId) async {
|
||||
Future<void> connect(
|
||||
String userId, {
|
||||
DatabaseProvider databaseProvider, // Used only for testing
|
||||
}) async {
|
||||
if (db != null) {
|
||||
throw Exception(
|
||||
'An instance of StreamChatDatabase is already connected.\n'
|
||||
'disconnect the previous instance before connecting again.',
|
||||
);
|
||||
}
|
||||
switch (_connectionMode) {
|
||||
case ConnectionMode.regular:
|
||||
_logger.info('Connecting on a regular isolate');
|
||||
db = MoorChatDatabase(userId);
|
||||
return;
|
||||
case ConnectionMode.background:
|
||||
_logger.info('Connecting on background isolate');
|
||||
db = SharedDB.constructMoorChatDatabase(userId);
|
||||
return;
|
||||
}
|
||||
db = databaseProvider?.call(userId, _connectionMode) ??
|
||||
_defaultDatabaseProvider(userId, _connectionMode);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -259,9 +242,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
||||
@override
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
bool clearQueryCache,
|
||||
) =>
|
||||
List<String> cids, {
|
||||
bool clearQueryCache = false,
|
||||
}) =>
|
||||
_readProtected(() async {
|
||||
_logger.info('updateChannelQueries');
|
||||
return db.channelQueryDao.updateChannelQueries(
|
||||
|
||||
@@ -0,0 +1,798 @@
|
||||
{
|
||||
"_meta": {
|
||||
"description": "This file contains a serialized version of schema entities for moor.",
|
||||
"version": "0.1.0-dev-preview"
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"id": 0,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "channels",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "cid",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "config",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<Object>()",
|
||||
"dart_type_name": "Map<String, Object>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "frozen",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": "Constant(false)",
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "last_message_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "deleted_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "member_count",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_by_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extra_data",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<Object>()",
|
||||
"dart_type_name": "Map<String, Object>"
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"cid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "messages",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "message_text",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "attachments",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "ListConverter<String>()",
|
||||
"dart_type_name": "List<String>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MessageSendingStatusConverter()",
|
||||
"dart_type_name": "MessageSendingStatus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "mentioned_users",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "ListConverter<String>()",
|
||||
"dart_type_name": "List<String>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reaction_counts",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<int>()",
|
||||
"dart_type_name": "Map<String, int>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reaction_scores",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<int>()",
|
||||
"dart_type_name": "Map<String, int>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "parent_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "quoted_message_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "reply_count",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "show_in_channel",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "shadowed",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "deleted_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "channel_cid",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": "NULLABLE REFERENCES channels(cid) ON DELETE CASCADE",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extra_data",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<Object>()",
|
||||
"dart_type_name": "Map<String, Object>"
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "reactions",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "user_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "message_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": "REFERENCES messages(id) ON DELETE CASCADE",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "score",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extra_data",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<Object>()",
|
||||
"dart_type_name": "Map<String, Object>"
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"message_id",
|
||||
"type",
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "users",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "last_active",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "online",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "banned",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "extra_data",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<Object>()",
|
||||
"dart_type_name": "Map<String, Object>"
|
||||
}
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "members",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "user_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "channel_cid",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "invite_accepted_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "invite_rejected_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "invited",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "banned",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "shadow_banned",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "is_moderator",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"user_id",
|
||||
"channel_cid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "reads",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "last_read",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "channel_cid",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": "REFERENCES channels(cid) ON DELETE CASCADE",
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "unread_messages",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"user_id",
|
||||
"channel_cid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "channel_queries",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "query_hash",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "channel_cid",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"query_hash",
|
||||
"channel_cid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"references": [],
|
||||
"type": "table",
|
||||
"data": {
|
||||
"name": "connection_events",
|
||||
"was_declared_in_moor": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "own_user",
|
||||
"moor_type": "ColumnType.text",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": [],
|
||||
"type_converter": {
|
||||
"dart_expr": "MapConverter<Object>()",
|
||||
"dart_type_name": "Map<String, Object>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "total_unread_count",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "unread_channels",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "last_event_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
},
|
||||
{
|
||||
"name": "last_sync_at",
|
||||
"moor_type": "ColumnType.datetime",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
"default_client_dart": null,
|
||||
"dsl_features": []
|
||||
}
|
||||
],
|
||||
"is_virtual": false,
|
||||
"explicit_pk": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,13 @@ dependencies:
|
||||
stream_chat:
|
||||
path: ../stream_chat
|
||||
|
||||
dependency_overrides:
|
||||
stream_chat:
|
||||
path: ../stream_chat
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^1.11.0
|
||||
mocktail: ^0.1.0
|
||||
moor_generator: ^4.2.0
|
||||
pedantic: ^1.11.0
|
||||
test: ^1.16.0
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
class MockChatDatabase extends Mock implements MoorChatDatabase {
|
||||
UserDao _userDao;
|
||||
|
||||
@override
|
||||
UserDao get userDao => _userDao ??= MockUserDao();
|
||||
|
||||
ChannelDao _channelDao;
|
||||
|
||||
@override
|
||||
ChannelDao get channelDao => _channelDao ??= MockChannelDao();
|
||||
|
||||
MessageDao _messageDao;
|
||||
|
||||
@override
|
||||
MessageDao get messageDao => _messageDao ??= MockMessageDao();
|
||||
|
||||
PinnedMessageDao _pinnedMessageDao;
|
||||
|
||||
@override
|
||||
PinnedMessageDao get pinnedMessageDao =>
|
||||
_pinnedMessageDao ??= MockPinnedMessageDao();
|
||||
|
||||
MemberDao _memberDao;
|
||||
|
||||
@override
|
||||
MemberDao get memberDao => _memberDao ??= MockMemberDao();
|
||||
|
||||
ReactionDao _reactionDao;
|
||||
|
||||
@override
|
||||
ReactionDao get reactionDao => _reactionDao ??= MockReactionDao();
|
||||
|
||||
ReadDao _readDao;
|
||||
|
||||
@override
|
||||
ReadDao get readDao => _readDao ??= MockReadDao();
|
||||
|
||||
ChannelQueryDao _channelQueryDao;
|
||||
|
||||
@override
|
||||
ChannelQueryDao get channelQueryDao =>
|
||||
_channelQueryDao ??= MockChannelQueryDao();
|
||||
|
||||
ConnectionEventDao _connectionEventDao;
|
||||
|
||||
@override
|
||||
ConnectionEventDao get connectionEventDao =>
|
||||
_connectionEventDao ??= MockConnectionEventDao();
|
||||
}
|
||||
|
||||
class MockUserDao extends Mock implements UserDao {}
|
||||
|
||||
class MockChannelDao extends Mock implements ChannelDao {}
|
||||
|
||||
class MockMessageDao extends Mock implements MessageDao {}
|
||||
|
||||
class MockPinnedMessageDao extends Mock implements PinnedMessageDao {}
|
||||
|
||||
class MockMemberDao extends Mock implements MemberDao {}
|
||||
|
||||
class MockReactionDao extends Mock implements ReactionDao {}
|
||||
|
||||
class MockReadDao extends Mock implements ReadDao {}
|
||||
|
||||
class MockChannelQueryDao extends Mock implements ChannelQueryDao {}
|
||||
|
||||
class MockConnectionEventDao extends Mock implements ConnectionEventDao {}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/list_converter.dart';
|
||||
|
||||
void main() {
|
||||
group('mapToDart', () {
|
||||
final listConverter = ListConverter<String>();
|
||||
|
||||
test('should return null if nothing is provided', () {
|
||||
final res = listConverter.mapToDart(null);
|
||||
expect(res, isNull);
|
||||
});
|
||||
|
||||
test('should throw type error if the provided json is not a list', () {
|
||||
final json = {'test_key': 'testData'};
|
||||
expect(
|
||||
() => listConverter.mapToDart(jsonEncode(json)),
|
||||
throwsA(isA<TypeError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw type error if the provided json is not a list of String',
|
||||
() {
|
||||
final json = [22, 33, 44];
|
||||
expect(
|
||||
() => listConverter.mapToDart(jsonEncode(json)),
|
||||
throwsA(isA<TypeError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return list of String if json data list is provided', () {
|
||||
final data = ['data1', 'data2', 'data3'];
|
||||
final res = listConverter.mapToDart(jsonEncode(data));
|
||||
expect(res.length, data.length);
|
||||
});
|
||||
});
|
||||
|
||||
group('mapToSql', () {
|
||||
final listConverter = ListConverter<String>();
|
||||
|
||||
test('should return null if nothing is provided', () {
|
||||
final res = listConverter.mapToSql(null);
|
||||
expect(res, isNull);
|
||||
});
|
||||
|
||||
test('should return json string if data list is provided', () {
|
||||
final data = ['data1', 'data2', 'data3'];
|
||||
final res = listConverter.mapToSql(data);
|
||||
expect(res, jsonEncode(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
void main() {
|
||||
group('mapToDart', () {
|
||||
final mapConverter = MapConverter<String>();
|
||||
|
||||
test('should return null if nothing is provided', () {
|
||||
final res = mapConverter.mapToDart(null);
|
||||
expect(res, isNull);
|
||||
});
|
||||
|
||||
test('should throw type error if the provided json is not a map', () {
|
||||
const json = ['testData1', 'testData2', 'testData3'];
|
||||
expect(
|
||||
() => mapConverter.mapToDart(jsonEncode(json)),
|
||||
throwsA(isA<TypeError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'should throw type error if the provided json is not a '
|
||||
'map of String, String',
|
||||
() {
|
||||
const json = {'test_key': 22, 'test_key2': 33, 'test_key3': 44};
|
||||
expect(
|
||||
() => mapConverter.mapToDart(jsonEncode(json)),
|
||||
throwsA(isA<TypeError>()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('should return map of String, String if json data is provided', () {
|
||||
const data = {
|
||||
'test_key': 'testValue',
|
||||
'test_key2': 'testValue2',
|
||||
'test_key3': 'testValue3',
|
||||
};
|
||||
final res = mapConverter.mapToDart(jsonEncode(data));
|
||||
expect(res, data);
|
||||
});
|
||||
});
|
||||
|
||||
group('mapToSql', () {
|
||||
final mapConverter = MapConverter<String>();
|
||||
|
||||
test('should return null if nothing is provided', () {
|
||||
final res = mapConverter.mapToSql(null);
|
||||
expect(res, isNull);
|
||||
});
|
||||
|
||||
test('should return json string if data map is provided', () {
|
||||
const data = {
|
||||
'test_key': 'testValue',
|
||||
'test_key2': 'testValue2',
|
||||
'test_key3': 'testValue3',
|
||||
};
|
||||
final res = mapConverter.mapToSql(data);
|
||||
expect(res, jsonEncode(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart';
|
||||
|
||||
void main() {
|
||||
group('mapToDart', () {
|
||||
final statusConverter = MessageSendingStatusConverter();
|
||||
|
||||
test('should return null if nothing is provided', () {
|
||||
final res = statusConverter.mapToDart(null);
|
||||
expect(res, isNull);
|
||||
});
|
||||
|
||||
test('should return expected status if status code is provided', () {
|
||||
final res = statusConverter.mapToDart(3);
|
||||
expect(res, MessageSendingStatus.updating);
|
||||
});
|
||||
});
|
||||
|
||||
group('mapToSql', () {
|
||||
final statusConverter = MessageSendingStatusConverter();
|
||||
|
||||
test('should return null if nothing is provided', () {
|
||||
final res = statusConverter.mapToSql(null);
|
||||
expect(res, isNull);
|
||||
});
|
||||
|
||||
test('should return expected code if the status is provided', () {
|
||||
final res = statusConverter.mapToSql(MessageSendingStatus.updating);
|
||||
expect(res, 3);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/channel_dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
ChannelDao channelDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
channelDao = database.channelDao;
|
||||
});
|
||||
|
||||
test('getChannelByCid', () async {
|
||||
const id = 'testId';
|
||||
const cid = 'testCid';
|
||||
const type = 'testType';
|
||||
|
||||
// Should be null initially
|
||||
final channel = await channelDao.getChannelByCid(cid);
|
||||
expect(channel, isNull);
|
||||
|
||||
// Saving a dummy channel
|
||||
final dummyChannel = ChannelModel(
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: ChannelConfig(),
|
||||
);
|
||||
await channelDao.updateChannels([dummyChannel]);
|
||||
|
||||
// Should match the dummy channel
|
||||
final updatedChannel = await channelDao.getChannelByCid(cid);
|
||||
expect(updatedChannel.id, id);
|
||||
expect(updatedChannel.cid, cid);
|
||||
expect(updatedChannel.type, type);
|
||||
});
|
||||
|
||||
test('deleteChannelByCids', () async {
|
||||
const id = 'testId';
|
||||
const cid = 'testCid';
|
||||
const type = 'testType';
|
||||
|
||||
// Saving a dummy channel
|
||||
final dummyChannel = ChannelModel(
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: ChannelConfig(),
|
||||
);
|
||||
await channelDao.updateChannels([dummyChannel]);
|
||||
|
||||
// Should match the dummy channel
|
||||
final updatedChannel = await channelDao.getChannelByCid(cid);
|
||||
expect(updatedChannel.id, id);
|
||||
expect(updatedChannel.cid, cid);
|
||||
expect(updatedChannel.type, type);
|
||||
|
||||
// Deleting the dummyChannel using cid
|
||||
await channelDao.deleteChannelByCids([cid]);
|
||||
|
||||
// Fetched channel Should be null
|
||||
final channel = await channelDao.getChannelByCid(cid);
|
||||
expect(channel, isNull);
|
||||
});
|
||||
|
||||
test('cids', () async {
|
||||
// Should be empty initially
|
||||
final cids = await channelDao.cids;
|
||||
expect(cids, []);
|
||||
|
||||
const id = 'testId';
|
||||
const cid = 'testCid';
|
||||
const type = 'testType';
|
||||
|
||||
// Saving a dummy channel
|
||||
final dummyChannel = ChannelModel(
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: ChannelConfig(),
|
||||
);
|
||||
await channelDao.updateChannels([dummyChannel]);
|
||||
|
||||
// Should return the cid of the dummy channel
|
||||
final updatedCids = await channelDao.cids;
|
||||
expect(updatedCids, [cid]);
|
||||
});
|
||||
|
||||
test('updateChannels', () async {
|
||||
const id = 'testId';
|
||||
const cid = 'testCid';
|
||||
const type = 'testType';
|
||||
|
||||
// Should be null initially
|
||||
final channel = await channelDao.getChannelByCid(cid);
|
||||
expect(channel, isNull);
|
||||
|
||||
// Saving a dummy channel
|
||||
final dummyChannel = ChannelModel(
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: ChannelConfig(),
|
||||
);
|
||||
await channelDao.updateChannels([dummyChannel]);
|
||||
|
||||
// Should match the dummy channel
|
||||
final updatedChannel = await channelDao.getChannelByCid(cid);
|
||||
expect(updatedChannel.id, id);
|
||||
expect(updatedChannel.cid, cid);
|
||||
expect(updatedChannel.type, type);
|
||||
|
||||
// Updating the previously saved channel
|
||||
const newType = 'newTestType';
|
||||
final newChannel = dummyChannel.copyWith(type: newType);
|
||||
await channelDao.updateChannels([newChannel]);
|
||||
|
||||
// Should match the new channel
|
||||
final newUpdatedChannel = await channelDao.getChannelByCid(cid);
|
||||
expect(newUpdatedChannel.id, id);
|
||||
expect(newUpdatedChannel.cid, cid);
|
||||
expect(newUpdatedChannel.type, newType);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/channel_query_dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
MoorChatDatabase database;
|
||||
ChannelQueryDao channelQueryDao;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
channelQueryDao = database.channelQueryDao;
|
||||
});
|
||||
|
||||
test('updateChannelQueries', () async {
|
||||
const filter = {
|
||||
'members': {
|
||||
r'$in': ['testUserId'],
|
||||
},
|
||||
};
|
||||
|
||||
const cids = ['testCid1', 'testCid2', 'testCid3'];
|
||||
|
||||
final cachedCids = await channelQueryDao.getCachedChannelCids(filter);
|
||||
expect(cachedCids, isEmpty);
|
||||
|
||||
// Updating channel queries
|
||||
await channelQueryDao.updateChannelQueries(filter, cids);
|
||||
|
||||
final updatedCids = await channelQueryDao.getCachedChannelCids(filter);
|
||||
expect(updatedCids, cids);
|
||||
});
|
||||
|
||||
test('clear queryCache before updateChannelQueries', () async {
|
||||
const filter = {
|
||||
'members': {
|
||||
r'$in': ['testUserId'],
|
||||
},
|
||||
};
|
||||
|
||||
const cids = ['testCid1', 'testCid2', 'testCid3'];
|
||||
|
||||
final cachedCids = await channelQueryDao.getCachedChannelCids(filter);
|
||||
expect(cachedCids, isEmpty);
|
||||
|
||||
// Updating channel queries
|
||||
await channelQueryDao.updateChannelQueries(
|
||||
filter,
|
||||
cids,
|
||||
clearQueryCache: true,
|
||||
);
|
||||
|
||||
final updatedCids = await channelQueryDao.getCachedChannelCids(filter);
|
||||
expect(updatedCids, cids);
|
||||
});
|
||||
|
||||
test('getCachedChannelCids', () async {
|
||||
const filter = {
|
||||
'members': {
|
||||
r'$in': ['testUserId'],
|
||||
},
|
||||
};
|
||||
|
||||
const cids = ['testCid1', 'testCid2', 'testCid3'];
|
||||
|
||||
final cachedCids = await channelQueryDao.getCachedChannelCids(filter);
|
||||
expect(cachedCids, isEmpty);
|
||||
|
||||
// Updating channel queries
|
||||
await channelQueryDao.updateChannelQueries(filter, cids);
|
||||
|
||||
final updatedCids = await channelQueryDao.getCachedChannelCids(filter);
|
||||
expect(updatedCids, cids);
|
||||
});
|
||||
|
||||
Future<List<ChannelModel>> _insertTestDataForGetChannel(
|
||||
Map<String, Object> filter, {
|
||||
int count = 3,
|
||||
}) async {
|
||||
final now = DateTime.now();
|
||||
final userDao = database.userDao;
|
||||
final channelDao = database.channelDao;
|
||||
|
||||
final cids = List.generate(count, (index) => 'testCid$index');
|
||||
final users = List.generate(count, (index) => User(id: 'testId$index'));
|
||||
final channels = List.generate(
|
||||
count,
|
||||
(index) => ChannelModel(
|
||||
id: 'testId$index',
|
||||
type: 'testType$index',
|
||||
cid: cids[index],
|
||||
createdBy: users[index],
|
||||
config: ChannelConfig(),
|
||||
extraData: {'test_custom_field': math.Random().nextInt(100)},
|
||||
createdAt: now,
|
||||
memberCount: math.Random().nextInt(100),
|
||||
lastMessageAt: now.add(Duration(hours: index)),
|
||||
),
|
||||
).reversed.toList(growable: false);
|
||||
|
||||
await userDao.updateUsers(users);
|
||||
await channelDao.updateChannels(channels);
|
||||
await channelQueryDao.updateChannelQueries(filter, cids);
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
group('getChannels', () {
|
||||
const filter = {
|
||||
'members': {
|
||||
r'$in': ['testUserId'],
|
||||
},
|
||||
};
|
||||
|
||||
test('should return empty list of channels', () async {
|
||||
final channels = await channelQueryDao.getChannels(filter: filter);
|
||||
expect(channels, isEmpty);
|
||||
});
|
||||
|
||||
test('should return all the inserted channels', () async {
|
||||
// Inserting test data for get channels
|
||||
final insertedChannels = await _insertTestDataForGetChannel(filter);
|
||||
|
||||
// Should match with the inserted channels
|
||||
final updatedChannels = await channelQueryDao.getChannels(filter: filter);
|
||||
expect(updatedChannels.length, insertedChannels.length);
|
||||
for (var i = 0; i < updatedChannels.length; i++) {
|
||||
final updatedChannel = updatedChannels[i];
|
||||
final insertedChannel = insertedChannels[i];
|
||||
|
||||
// Should match all the basic details
|
||||
expect(updatedChannel.id, insertedChannel.id);
|
||||
expect(updatedChannel.type, insertedChannel.type);
|
||||
expect(updatedChannel.cid, insertedChannel.cid);
|
||||
expect(updatedChannel.memberCount, insertedChannel.memberCount);
|
||||
|
||||
// Should match createdAt date
|
||||
expect(
|
||||
updatedChannel.createdAt,
|
||||
isSameDateAs(insertedChannel.createdAt),
|
||||
);
|
||||
|
||||
// Should match lastMessageAt date
|
||||
expect(
|
||||
updatedChannel.lastMessageAt,
|
||||
isSameDateAs(insertedChannel.lastMessageAt),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
'should return all the inserted channels along with pagination applied',
|
||||
() async {
|
||||
const offset = 5;
|
||||
const limit = 15;
|
||||
const pagination = PaginationParams(offset: offset, limit: limit);
|
||||
|
||||
// Inserting test data for get channels
|
||||
final insertedChannels = await _insertTestDataForGetChannel(
|
||||
filter,
|
||||
count: 30,
|
||||
);
|
||||
|
||||
// Should match with the inserted channels
|
||||
final updatedChannels = await channelQueryDao.getChannels(
|
||||
filter: filter,
|
||||
paginationParams: pagination,
|
||||
);
|
||||
expect(updatedChannels.length, limit);
|
||||
expect(updatedChannels.first.id, 'testId24');
|
||||
expect(updatedChannels.first.cid, 'testCid24');
|
||||
},
|
||||
);
|
||||
|
||||
test('should return sorted channels using member count', () async {
|
||||
int sortComparator(ChannelModel a, ChannelModel b) =>
|
||||
b.memberCount.compareTo(a.memberCount);
|
||||
|
||||
// Inserting test data for get channels
|
||||
final insertedChannels = await _insertTestDataForGetChannel(filter);
|
||||
insertedChannels.sort(sortComparator);
|
||||
|
||||
// Should match with the inserted channels
|
||||
final updatedChannels = await channelQueryDao.getChannels(
|
||||
filter: filter,
|
||||
sort: [SortOption('member_count', comparator: sortComparator)],
|
||||
);
|
||||
|
||||
expect(updatedChannels.length, insertedChannels.length);
|
||||
for (var i = 0; i < updatedChannels.length; i++) {
|
||||
final updatedChannel = updatedChannels[i];
|
||||
final insertedChannel = insertedChannels[i];
|
||||
|
||||
// Should match all the basic details
|
||||
expect(updatedChannel.id, insertedChannel.id);
|
||||
expect(updatedChannel.type, insertedChannel.type);
|
||||
expect(updatedChannel.cid, insertedChannel.cid);
|
||||
expect(updatedChannel.memberCount, insertedChannel.memberCount);
|
||||
|
||||
// Should match createdAt date
|
||||
expect(
|
||||
updatedChannel.createdAt,
|
||||
isSameDateAs(insertedChannel.createdAt),
|
||||
);
|
||||
|
||||
// Should match lastMessageAt date
|
||||
expect(
|
||||
updatedChannel.lastMessageAt,
|
||||
isSameDateAs(insertedChannel.lastMessageAt),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('should throw if comparator is not provided in sort list', () {
|
||||
expect(
|
||||
() => channelQueryDao.getChannels(
|
||||
sort: [const SortOption('test_custom_field')],
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return sorted channels using custom field', () async {
|
||||
int sortComparator(ChannelModel a, ChannelModel b) {
|
||||
final aData = a.extraData['test_custom_field'] as int;
|
||||
final bData = b.extraData['test_custom_field'] as int;
|
||||
return bData.compareTo(aData);
|
||||
}
|
||||
|
||||
// Inserting test data for get channels
|
||||
final insertedChannels = await _insertTestDataForGetChannel(filter);
|
||||
insertedChannels.sort(sortComparator);
|
||||
|
||||
// Should match with the inserted channels
|
||||
final updatedChannels = await channelQueryDao.getChannels(
|
||||
filter: filter,
|
||||
sort: [SortOption('test_custom_field', comparator: sortComparator)],
|
||||
);
|
||||
|
||||
expect(updatedChannels.length, insertedChannels.length);
|
||||
for (var i = 0; i < updatedChannels.length; i++) {
|
||||
final updatedChannel = updatedChannels[i];
|
||||
final insertedChannel = insertedChannels[i];
|
||||
|
||||
// Should match all the basic details
|
||||
expect(updatedChannel.id, insertedChannel.id);
|
||||
expect(updatedChannel.type, insertedChannel.type);
|
||||
expect(updatedChannel.cid, insertedChannel.cid);
|
||||
expect(updatedChannel.memberCount, insertedChannel.memberCount);
|
||||
|
||||
// Should match createdAt date
|
||||
expect(
|
||||
updatedChannel.createdAt,
|
||||
isSameDateAs(insertedChannel.createdAt),
|
||||
);
|
||||
|
||||
// Should match lastMessageAt date
|
||||
expect(
|
||||
updatedChannel.lastMessageAt,
|
||||
isSameDateAs(insertedChannel.lastMessageAt),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/connection_event_dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
ConnectionEventDao eventDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
eventDao = database.connectionEventDao;
|
||||
});
|
||||
|
||||
test('connectionEvent', () async {
|
||||
// Should be null initially
|
||||
final event = await eventDao.connectionEvent;
|
||||
expect(event, isNull);
|
||||
|
||||
// Adding a new event
|
||||
final newEvent = Event(
|
||||
createdAt: DateTime.now(),
|
||||
totalUnreadCount: 33,
|
||||
unreadChannels: 3,
|
||||
me: OwnUser(id: 'testUserId'),
|
||||
);
|
||||
await eventDao.updateConnectionEvent(newEvent);
|
||||
|
||||
// Should match the added event
|
||||
final updatedEvent = await eventDao.connectionEvent;
|
||||
expect(updatedEvent.me.id, newEvent.me.id);
|
||||
expect(updatedEvent.totalUnreadCount, newEvent.totalUnreadCount);
|
||||
expect(updatedEvent.unreadChannels, newEvent.unreadChannels);
|
||||
});
|
||||
|
||||
test('lastSyncAt', () async {
|
||||
// Should be null initially
|
||||
final lastSyncAt = await eventDao.lastSyncAt;
|
||||
expect(lastSyncAt, isNull);
|
||||
|
||||
// Adding an event for testing
|
||||
final event = Event(
|
||||
createdAt: DateTime.now(),
|
||||
totalUnreadCount: 33,
|
||||
unreadChannels: 3,
|
||||
me: OwnUser(id: 'testUserId'),
|
||||
);
|
||||
await eventDao.updateConnectionEvent(event);
|
||||
|
||||
// Updating it's last sync
|
||||
final now = DateTime.now();
|
||||
await eventDao.updateLastSyncAt(now);
|
||||
|
||||
// Should match the updated last sync
|
||||
final updatedLastSyncAt = await eventDao.lastSyncAt;
|
||||
expect(updatedLastSyncAt, isSameDateAs(now));
|
||||
});
|
||||
|
||||
test('updateConnectionEvent', () async {
|
||||
// Adding and event for testing
|
||||
final event = Event(
|
||||
createdAt: DateTime.now(),
|
||||
totalUnreadCount: 33,
|
||||
unreadChannels: 3,
|
||||
me: OwnUser(id: 'testUserId'),
|
||||
);
|
||||
await eventDao.updateConnectionEvent(event);
|
||||
|
||||
// Should match the previously added event
|
||||
final fetchedEvent = await eventDao.connectionEvent;
|
||||
expect(fetchedEvent.me.id, event.me.id);
|
||||
expect(fetchedEvent.totalUnreadCount, event.totalUnreadCount);
|
||||
expect(fetchedEvent.unreadChannels, event.unreadChannels);
|
||||
|
||||
// Updating the added event
|
||||
final newEvent = event.copyWith(unreadChannels: 4);
|
||||
await eventDao.updateConnectionEvent(newEvent);
|
||||
|
||||
// Should match the updated event
|
||||
final fetchedNewEvent = await eventDao.connectionEvent;
|
||||
expect(fetchedNewEvent.me.id, event.me.id);
|
||||
expect(fetchedNewEvent.totalUnreadCount, event.totalUnreadCount);
|
||||
expect(fetchedNewEvent.unreadChannels, newEvent.unreadChannels);
|
||||
});
|
||||
|
||||
test('updateLastSyncAt', () async {
|
||||
// Should be null initially
|
||||
final lastSyncAt = await eventDao.lastSyncAt;
|
||||
expect(lastSyncAt, isNull);
|
||||
|
||||
// Adding an event just for testing
|
||||
final event = Event(
|
||||
createdAt: DateTime.now(),
|
||||
totalUnreadCount: 33,
|
||||
unreadChannels: 3,
|
||||
me: OwnUser(id: 'testUserId'),
|
||||
);
|
||||
await eventDao.updateConnectionEvent(event);
|
||||
|
||||
// Updating it's last sync
|
||||
final now = DateTime.now();
|
||||
await eventDao.updateLastSyncAt(now);
|
||||
|
||||
// Should match the last sync
|
||||
final updatedLastSyncAt = await eventDao.lastSyncAt;
|
||||
expect(updatedLastSyncAt, isSameDateAs(now));
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
MemberDao memberDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
memberDao = database.memberDao;
|
||||
});
|
||||
|
||||
Future<List<Member>> _prepareTestData(String cid) async {
|
||||
final users = List.generate(3, (index) => User(id: 'testUserId$index'));
|
||||
final memberList = List.generate(
|
||||
3,
|
||||
(index) => Member(
|
||||
user: users[index],
|
||||
banned: math.Random().nextBool(),
|
||||
shadowBanned: math.Random().nextBool(),
|
||||
createdAt: DateTime.now(),
|
||||
isModerator: math.Random().nextBool(),
|
||||
invited: math.Random().nextBool(),
|
||||
inviteAcceptedAt: DateTime.now(),
|
||||
role: 'testRole',
|
||||
updatedAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
await database.userDao.updateUsers(users);
|
||||
await memberDao.updateMembers(cid, memberList);
|
||||
return memberList;
|
||||
}
|
||||
|
||||
test('getMembersByCid', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Should be empty initially
|
||||
final members = await memberDao.getMembersByCid(cid);
|
||||
expect(members, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final memberList = await _prepareTestData(cid);
|
||||
|
||||
// Should match the previous test data
|
||||
final fetchedMembers = await memberDao.getMembersByCid(cid);
|
||||
expect(fetchedMembers.length, memberList.length);
|
||||
for (var i = 0; i < fetchedMembers.length; i++) {
|
||||
final member = memberList[i];
|
||||
final fetchedMember = fetchedMembers[i];
|
||||
expect(fetchedMember.user.id, member.user.id);
|
||||
expect(fetchedMember.banned, member.banned);
|
||||
expect(fetchedMember.shadowBanned, member.shadowBanned);
|
||||
expect(fetchedMember.createdAt, isSameDateAs(member.createdAt));
|
||||
expect(fetchedMember.isModerator, member.isModerator);
|
||||
expect(fetchedMember.invited, member.invited);
|
||||
expect(fetchedMember.role, member.role);
|
||||
expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt));
|
||||
expect(
|
||||
fetchedMember.inviteAcceptedAt,
|
||||
isSameDateAs(member.inviteAcceptedAt),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('updateMembers', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final memberList = await _prepareTestData(cid);
|
||||
|
||||
// Should match the previous test data
|
||||
final fetchedMembers = await memberDao.getMembersByCid(cid);
|
||||
expect(fetchedMembers.length, memberList.length);
|
||||
for (var i = 0; i < fetchedMembers.length; i++) {
|
||||
final member = memberList[i];
|
||||
final fetchedMember = fetchedMembers[i];
|
||||
expect(fetchedMember.user.id, member.user.id);
|
||||
expect(fetchedMember.banned, member.banned);
|
||||
expect(fetchedMember.shadowBanned, member.shadowBanned);
|
||||
expect(fetchedMember.createdAt, isSameDateAs(member.createdAt));
|
||||
expect(fetchedMember.isModerator, member.isModerator);
|
||||
expect(fetchedMember.invited, member.invited);
|
||||
expect(fetchedMember.role, member.role);
|
||||
expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt));
|
||||
expect(
|
||||
fetchedMember.inviteAcceptedAt,
|
||||
isSameDateAs(member.inviteAcceptedAt),
|
||||
);
|
||||
}
|
||||
|
||||
// Modifying one of the member and also adding one new
|
||||
final copyMember = fetchedMembers.first.copyWith(banned: true);
|
||||
final newUser = User(id: 'testUserId3');
|
||||
final newMember = Member(
|
||||
user: newUser,
|
||||
banned: math.Random().nextBool(),
|
||||
shadowBanned: math.Random().nextBool(),
|
||||
createdAt: DateTime.now(),
|
||||
isModerator: math.Random().nextBool(),
|
||||
invited: math.Random().nextBool(),
|
||||
inviteAcceptedAt: DateTime.now(),
|
||||
role: 'testRole',
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
await database.userDao.updateUsers([newUser]);
|
||||
await memberDao.updateMembers(cid, [copyMember, newMember]);
|
||||
|
||||
// Fetched member length should be one more than inserted members.
|
||||
// copyMember `banned` modified field should be true.
|
||||
// Fetched members should contain the newMember.
|
||||
final newFetchedMembers = await memberDao.getMembersByCid(cid);
|
||||
expect(newFetchedMembers.length, fetchedMembers.length + 1);
|
||||
expect(
|
||||
newFetchedMembers
|
||||
.firstWhere((it) => it.user.id == copyMember.user.id)
|
||||
.banned,
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
newFetchedMembers
|
||||
.where((it) => it.user.id == newMember.user.id)
|
||||
.isNotEmpty,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('deleteMemberByCids', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final members = await _prepareTestData(cid);
|
||||
final fetchedMembers = await memberDao.getMembersByCid(cid);
|
||||
expect(members.length, fetchedMembers.length);
|
||||
|
||||
// Deleting all the members
|
||||
await memberDao.deleteMemberByCids([cid]);
|
||||
|
||||
// Fetched member list should be empty
|
||||
final newFetchedMembers = await memberDao.getMembersByCid(cid);
|
||||
expect(newFetchedMembers, isEmpty);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
MessageDao messageDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
messageDao = database.messageDao;
|
||||
});
|
||||
|
||||
Future<List<Message>> _prepareTestData(
|
||||
String cid, {
|
||||
bool quoted = false,
|
||||
bool threads = false,
|
||||
bool mapAllThreadToFirstMessage = false,
|
||||
int count = 3,
|
||||
}) async {
|
||||
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
|
||||
final messages = List.generate(
|
||||
count,
|
||||
(index) => Message(
|
||||
id: 'testMessageId$cid$index',
|
||||
type: 'testType',
|
||||
user: users[index],
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
replyCount: index,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #$index',
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId$index'),
|
||||
),
|
||||
);
|
||||
final quotedMessages = List.generate(
|
||||
count,
|
||||
(index) => Message(
|
||||
id: 'testQuotedMessageId$cid$index',
|
||||
type: 'testType',
|
||||
user: users[index],
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
replyCount: index,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #$index',
|
||||
quotedMessageId: messages[index].id,
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId$index'),
|
||||
),
|
||||
);
|
||||
final threadMessages = List.generate(
|
||||
count,
|
||||
(index) => Message(
|
||||
id: 'testThreadMessageId$cid$index',
|
||||
type: 'testType',
|
||||
user: users[index],
|
||||
parentId:
|
||||
mapAllThreadToFirstMessage ? messages[0].id : messages[index].id,
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
replyCount: index,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #$index',
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId$index'),
|
||||
),
|
||||
);
|
||||
final allMessages = [
|
||||
...messages,
|
||||
if (quoted) ...quotedMessages,
|
||||
if (threads) ...threadMessages
|
||||
];
|
||||
await database.userDao.updateUsers(users);
|
||||
await messageDao.updateMessages(cid, allMessages);
|
||||
return allMessages;
|
||||
}
|
||||
|
||||
test('deleteMessageByIds', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid);
|
||||
|
||||
// Fetched message list should match the test message list length
|
||||
final messages = await messageDao.getMessagesByCid(cid);
|
||||
expect(messages.length, insertedMessages.length);
|
||||
|
||||
// Deleting 2 messages from DB
|
||||
await messageDao.deleteMessageByIds(
|
||||
['testMessageId${cid}0', '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);
|
||||
});
|
||||
|
||||
group('deleteMessageByCids', () {
|
||||
const cid1 = 'testCid1';
|
||||
const cid2 = 'testCid2';
|
||||
|
||||
test('should delete all the messages 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);
|
||||
|
||||
// Deleting all the messages of cid1
|
||||
await messageDao.deleteMessageByCids([cid1]);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
test('should delete all the messages of both 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);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
|
||||
test('getMessageById', () async {
|
||||
const cid = 'testCid';
|
||||
const id = 'testMessageId${cid}0';
|
||||
|
||||
// Should be null initially
|
||||
final message = await messageDao.getMessageById(id);
|
||||
expect(message, isNull);
|
||||
|
||||
// Adding test message with the cid and id
|
||||
final insertedMessages = await _prepareTestData(cid, count: 1);
|
||||
expect(insertedMessages.first.id, id);
|
||||
|
||||
// Fetched message id should match the inserted message id
|
||||
final fetchedMessage = await messageDao.getMessageById(id);
|
||||
expect(fetchedMessage.id, insertedMessages.first.id);
|
||||
});
|
||||
|
||||
test('getThreadMessages', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Messages should be empty initially
|
||||
final messages = await messageDao.getThreadMessages(cid);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, threads: true);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Should fetch all the thread messages of cid
|
||||
final threadMessages = await messageDao.getThreadMessages(cid);
|
||||
expect(threadMessages, isNotEmpty);
|
||||
for (final message in threadMessages) {
|
||||
expect(message.parentId, isNotNull);
|
||||
}
|
||||
});
|
||||
|
||||
test('getThreadMessagesByParentId', () async {
|
||||
const cid = 'testCid';
|
||||
const parentId = 'testMessageId${cid}0';
|
||||
|
||||
// Messages should be empty initially
|
||||
final messages = await messageDao.getThreadMessagesByParentId(parentId);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, threads: true);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Should fetch all the thread messages of parentId
|
||||
final threadMessages =
|
||||
await messageDao.getThreadMessagesByParentId(parentId);
|
||||
expect(threadMessages.length, 1);
|
||||
expect(threadMessages.first.parentId, parentId);
|
||||
});
|
||||
|
||||
test('getThreadMessagesByParentId along with pagination', () async {
|
||||
const cid = 'testCid';
|
||||
const parentId = 'testMessageId${cid}0';
|
||||
const options = PaginationParams(
|
||||
limit: 15,
|
||||
lessThan: 'testThreadMessageId${cid}25',
|
||||
greaterThanOrEqual: 'testThreadMessageId${cid}5',
|
||||
);
|
||||
|
||||
// Messages should be empty initially
|
||||
final messages = await messageDao.getThreadMessagesByParentId(
|
||||
parentId,
|
||||
options: options,
|
||||
);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(
|
||||
cid,
|
||||
threads: true,
|
||||
mapAllThreadToFirstMessage: true,
|
||||
count: 30,
|
||||
);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Should fetch all the thread messages of parentId and apply the pagination
|
||||
final threadMessages = await messageDao.getThreadMessagesByParentId(
|
||||
parentId,
|
||||
options: options,
|
||||
);
|
||||
expect(threadMessages.length, 15);
|
||||
expect(threadMessages.first.parentId, parentId);
|
||||
});
|
||||
|
||||
test('getMessagesByCid', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Should be empty initially
|
||||
final messages = await messageDao.getMessagesByCid(cid);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Fetched message should match the inserted messages
|
||||
final fetchedMessages = await messageDao.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, insertedMessages.length);
|
||||
for (var i = 0; i < fetchedMessages.length; i++) {
|
||||
final fetchedMessage = fetchedMessages[i];
|
||||
final insertedMessage = insertedMessages[i];
|
||||
expect(fetchedMessage.id, insertedMessage.id);
|
||||
}
|
||||
});
|
||||
|
||||
test('getMessagesByCid along with quotedMessage', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Should be empty initially
|
||||
final messages = await messageDao.getMessagesByCid(cid);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, quoted: true);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Fetched message should match the inserted messages
|
||||
final fetchedMessages = await messageDao.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, insertedMessages.length);
|
||||
final quoted = fetchedMessages.where((it) => it.quotedMessage != null);
|
||||
expect(quoted.length, insertedMessages.length / 2);
|
||||
});
|
||||
|
||||
test('getMessagesByCid along with pagination', () async {
|
||||
const cid = 'testCid';
|
||||
const limit = 15;
|
||||
const lessThan = 'testMessageId${cid}25';
|
||||
const greaterThanOrEqual = 'testMessageId${cid}5';
|
||||
const pagination = PaginationParams(
|
||||
limit: limit,
|
||||
lessThan: lessThan,
|
||||
greaterThanOrEqual: greaterThanOrEqual,
|
||||
);
|
||||
|
||||
// Should be empty initially
|
||||
final messages = await messageDao.getMessagesByCid(
|
||||
cid,
|
||||
messagePagination: pagination,
|
||||
);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, count: 30);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Fetched message should match the inserted messages
|
||||
final fetchedMessages = await messageDao.getMessagesByCid(
|
||||
cid,
|
||||
messagePagination: pagination,
|
||||
);
|
||||
expect(fetchedMessages.length, limit);
|
||||
expect(fetchedMessages.first.id, greaterThanOrEqual);
|
||||
expect(fetchedMessages.last.id != lessThan, true);
|
||||
});
|
||||
|
||||
test('updateMessages', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Modifying one of the message and also adding one new
|
||||
final copyMessage = insertedMessages.first.copyWith(showInChannel: false);
|
||||
final newMessage = Message(
|
||||
id: 'testMessageId${cid}4',
|
||||
type: 'testType',
|
||||
user: User(id: 'testUserId4'),
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
showInChannel: math.Random().nextBool(),
|
||||
replyCount: 4,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #4',
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId4'),
|
||||
);
|
||||
|
||||
await messageDao.updateMessages(cid, [copyMessage, newMessage]);
|
||||
|
||||
// Fetched messages length should be one more than inserted message.
|
||||
// copyMessage `showInChannel` modified field should be false.
|
||||
// Fetched messages should contain the newMessage.
|
||||
final fetchedMessages = await messageDao.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, insertedMessages.length + 1);
|
||||
expect(
|
||||
fetchedMessages.firstWhere((it) => it.id == copyMessage.id).showInChannel,
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
fetchedMessages.map((it) => it.id).contains(newMessage.id),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
PinnedMessageDao pinnedMessageDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
pinnedMessageDao = database.pinnedMessageDao;
|
||||
});
|
||||
|
||||
Future<List<Message>> _prepareTestData(
|
||||
String cid, {
|
||||
bool quoted = false,
|
||||
bool threads = false,
|
||||
bool mapAllThreadToFirstMessage = false,
|
||||
int count = 3,
|
||||
}) async {
|
||||
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
|
||||
final messages = List.generate(
|
||||
count,
|
||||
(index) => Message(
|
||||
id: 'testMessageId$cid$index',
|
||||
type: 'testType',
|
||||
user: users[index],
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
replyCount: index,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #$index',
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId$index'),
|
||||
),
|
||||
);
|
||||
final quotedMessages = List.generate(
|
||||
count,
|
||||
(index) => Message(
|
||||
id: 'testQuotedMessageId$cid$index',
|
||||
type: 'testType',
|
||||
user: users[index],
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
replyCount: index,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #$index',
|
||||
quotedMessageId: messages[index].id,
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId$index'),
|
||||
),
|
||||
);
|
||||
final threadMessages = List.generate(
|
||||
count,
|
||||
(index) => Message(
|
||||
id: 'testThreadMessageId$cid$index',
|
||||
type: 'testType',
|
||||
user: users[index],
|
||||
parentId:
|
||||
mapAllThreadToFirstMessage ? messages[0].id : messages[index].id,
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
replyCount: index,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #$index',
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId$index'),
|
||||
),
|
||||
);
|
||||
final allMessages = [
|
||||
...messages,
|
||||
if (quoted) ...quotedMessages,
|
||||
if (threads) ...threadMessages
|
||||
];
|
||||
await database.userDao.updateUsers(users);
|
||||
await pinnedMessageDao.updateMessages(cid, allMessages);
|
||||
return allMessages;
|
||||
}
|
||||
|
||||
test('deleteMessageByIds', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid);
|
||||
|
||||
// Fetched message list should match the test message list length
|
||||
final messages = await pinnedMessageDao.getMessagesByCid(cid);
|
||||
expect(messages.length, insertedMessages.length);
|
||||
|
||||
// Deleting 2 messages from DB
|
||||
await pinnedMessageDao.deleteMessageByIds(
|
||||
['testMessageId${cid}0', '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);
|
||||
});
|
||||
|
||||
group('deleteMessageByCids', () {
|
||||
const cid1 = 'testCid1';
|
||||
const cid2 = 'testCid2';
|
||||
|
||||
test('should delete all the messages 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);
|
||||
|
||||
// Deleting all the messages of cid1
|
||||
await pinnedMessageDao.deleteMessageByCids([cid1]);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
test('should delete all the messages of both 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);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
|
||||
test('getMessageById', () async {
|
||||
const cid = 'testCid';
|
||||
const id = 'testMessageId${cid}0';
|
||||
|
||||
// Should be null initially
|
||||
final message = await pinnedMessageDao.getMessageById(id);
|
||||
expect(message, isNull);
|
||||
|
||||
// Adding test message with the cid and id
|
||||
final insertedMessages = await _prepareTestData(cid, count: 1);
|
||||
expect(insertedMessages.first.id, id);
|
||||
|
||||
// Fetched message id should match the inserted message id
|
||||
final fetchedMessage = await pinnedMessageDao.getMessageById(id);
|
||||
expect(fetchedMessage.id, insertedMessages.first.id);
|
||||
});
|
||||
|
||||
test('getThreadMessages', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Messages should be empty initially
|
||||
final messages = await pinnedMessageDao.getThreadMessages(cid);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, threads: true);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Should fetch all the thread messages of cid
|
||||
final threadMessages = await pinnedMessageDao.getThreadMessages(cid);
|
||||
expect(threadMessages, isNotEmpty);
|
||||
for (final message in threadMessages) {
|
||||
expect(message.parentId, isNotNull);
|
||||
}
|
||||
});
|
||||
|
||||
test('getThreadMessagesByParentId', () async {
|
||||
const cid = 'testCid';
|
||||
const parentId = 'testMessageId${cid}0';
|
||||
|
||||
// Messages should be empty initially
|
||||
final messages =
|
||||
await pinnedMessageDao.getThreadMessagesByParentId(parentId);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, threads: true);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Should fetch all the thread messages of parentId
|
||||
final threadMessages =
|
||||
await pinnedMessageDao.getThreadMessagesByParentId(parentId);
|
||||
expect(threadMessages.length, 1);
|
||||
expect(threadMessages.first.parentId, parentId);
|
||||
});
|
||||
|
||||
test('getThreadMessagesByParentId along with pagination', () async {
|
||||
const cid = 'testCid';
|
||||
const parentId = 'testMessageId${cid}0';
|
||||
const options = PaginationParams(
|
||||
limit: 15,
|
||||
lessThan: 'testThreadMessageId${cid}25',
|
||||
greaterThanOrEqual: 'testThreadMessageId${cid}5',
|
||||
);
|
||||
|
||||
// Messages should be empty initially
|
||||
final messages = await pinnedMessageDao.getThreadMessagesByParentId(
|
||||
parentId,
|
||||
options: options,
|
||||
);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(
|
||||
cid,
|
||||
threads: true,
|
||||
mapAllThreadToFirstMessage: true,
|
||||
count: 30,
|
||||
);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Should fetch all the thread messages of parentId and apply the pagination
|
||||
final threadMessages = await pinnedMessageDao.getThreadMessagesByParentId(
|
||||
parentId,
|
||||
options: options,
|
||||
);
|
||||
expect(threadMessages.length, 15);
|
||||
expect(threadMessages.first.parentId, parentId);
|
||||
});
|
||||
|
||||
test('getMessagesByCid', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Should be empty initially
|
||||
final messages = await pinnedMessageDao.getMessagesByCid(cid);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Fetched message should match the inserted messages
|
||||
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, insertedMessages.length);
|
||||
for (var i = 0; i < fetchedMessages.length; i++) {
|
||||
final fetchedMessage = fetchedMessages[i];
|
||||
final insertedMessage = insertedMessages[i];
|
||||
expect(fetchedMessage.id, insertedMessage.id);
|
||||
}
|
||||
});
|
||||
|
||||
test('getMessagesByCid along with quotedMessage', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Should be empty initially
|
||||
final messages = await pinnedMessageDao.getMessagesByCid(cid);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, quoted: true);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Fetched message should match the inserted messages
|
||||
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, insertedMessages.length);
|
||||
final quoted = fetchedMessages.where((it) => it.quotedMessage != null);
|
||||
expect(quoted.length, insertedMessages.length / 2);
|
||||
});
|
||||
|
||||
test('getMessagesByCid along with pagination', () async {
|
||||
const cid = 'testCid';
|
||||
const limit = 15;
|
||||
const lessThan = 'testMessageId${cid}25';
|
||||
const greaterThanOrEqual = 'testMessageId${cid}5';
|
||||
const pagination = PaginationParams(
|
||||
limit: limit,
|
||||
lessThan: lessThan,
|
||||
greaterThanOrEqual: greaterThanOrEqual,
|
||||
);
|
||||
|
||||
// Should be empty initially
|
||||
final messages = await pinnedMessageDao.getMessagesByCid(
|
||||
cid,
|
||||
messagePagination: pagination,
|
||||
);
|
||||
expect(messages, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid, count: 30);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Fetched message should match the inserted messages
|
||||
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(
|
||||
cid,
|
||||
messagePagination: pagination,
|
||||
);
|
||||
expect(fetchedMessages.length, limit);
|
||||
expect(fetchedMessages.first.id, greaterThanOrEqual);
|
||||
expect(fetchedMessages.last.id != lessThan, true);
|
||||
});
|
||||
|
||||
test('updateMessages', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final insertedMessages = await _prepareTestData(cid);
|
||||
expect(insertedMessages, isNotEmpty);
|
||||
|
||||
// Modifying one of the message and also adding one new
|
||||
final copyMessage = insertedMessages.first.copyWith(showInChannel: false);
|
||||
final newMessage = Message(
|
||||
id: 'testMessageId${cid}4',
|
||||
type: 'testType',
|
||||
user: User(id: 'testUserId4'),
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
showInChannel: math.Random().nextBool(),
|
||||
replyCount: 4,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_field': 'extraTestData'},
|
||||
text: 'Dummy text #4',
|
||||
pinned: math.Random().nextBool(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: User(id: 'testUserId4'),
|
||||
);
|
||||
|
||||
await pinnedMessageDao.updateMessages(cid, [copyMessage, newMessage]);
|
||||
|
||||
// Fetched messages length should be one more than inserted message.
|
||||
// copyMessage `showInChannel` modified field should be false.
|
||||
// Fetched messages should contain the newMessage.
|
||||
final fetchedMessages = await pinnedMessageDao.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, insertedMessages.length + 1);
|
||||
expect(
|
||||
fetchedMessages.firstWhere((it) => it.id == copyMessage.id).showInChannel,
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
fetchedMessages.map((it) => it.id).contains(newMessage.id),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/reaction_dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
ReactionDao reactionDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
reactionDao = database.reactionDao;
|
||||
});
|
||||
|
||||
Future<List<Reaction>> _prepareReactionData(
|
||||
String messageId, {
|
||||
String userId,
|
||||
int count = 3,
|
||||
}) async {
|
||||
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: {'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.messageDao.updateMessages('testCid', [message]);
|
||||
await reactionDao.updateReactions(reactions);
|
||||
|
||||
return reactions;
|
||||
}
|
||||
|
||||
test('getReactions', () async {
|
||||
const messageId = 'testMessageId';
|
||||
|
||||
// Should be empty initially
|
||||
final reactions = await reactionDao.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 reactionDao.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 reactionDao.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 reactionDao.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 reactionDao.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 reactionDao.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 reactionDao.getReactions(messageId1);
|
||||
final reactions2 = await reactionDao.getReactions(messageId2);
|
||||
expect(reactions1.length, insertedReactions1.length);
|
||||
expect(reactions2.length, insertedReactions2.length);
|
||||
|
||||
// Deleting all the reactions of messageId1
|
||||
await reactionDao.deleteReactionsByMessageIds([messageId1]);
|
||||
|
||||
// Fetched reactions length of only messageId1 should be empty
|
||||
final fetchedReactions1 = await reactionDao.getReactions(messageId1);
|
||||
final fetchedReactions2 = await reactionDao.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 reactionDao.getReactions(messageId1);
|
||||
final reactions2 = await reactionDao.getReactions(messageId2);
|
||||
expect(reactions1.length, insertedReactions1.length);
|
||||
expect(reactions2.length, insertedReactions2.length);
|
||||
|
||||
// Deleting all the reactions of messageId1 and messageId2
|
||||
await reactionDao.deleteReactionsByMessageIds([messageId1, messageId2]);
|
||||
|
||||
// Fetched reactions length of both messages should be empty
|
||||
final fetchedReactions1 = await reactionDao.getReactions(messageId1);
|
||||
final fetchedReactions2 = await reactionDao.getReactions(messageId2);
|
||||
expect(fetchedReactions1, isEmpty);
|
||||
expect(fetchedReactions2, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
ReadDao readDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
readDao = database.readDao;
|
||||
});
|
||||
|
||||
Future<List<Read>> _prepareReadData(String cid, {int count = 3}) async {
|
||||
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
|
||||
final reads = List.generate(
|
||||
count,
|
||||
(index) => Read(
|
||||
lastRead: DateTime.now(),
|
||||
user: users[index],
|
||||
unreadMessages: index + 10,
|
||||
),
|
||||
);
|
||||
|
||||
await database.userDao.updateUsers(users);
|
||||
await readDao.updateReads(cid, reads);
|
||||
return reads;
|
||||
}
|
||||
|
||||
test('getReadsByCid', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Should be empty initially
|
||||
final reads = await readDao.getReadsByCid(cid);
|
||||
expect(reads, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedReads = await _prepareReadData(cid);
|
||||
expect(insertedReads, isNotEmpty);
|
||||
|
||||
// Fetched reads should be equal to inserted reads
|
||||
final fetchedReads = await readDao.getReadsByCid(cid);
|
||||
expect(fetchedReads.length, insertedReads.length);
|
||||
for (var i = 0; i < fetchedReads.length; i++) {
|
||||
final fetchedRead = fetchedReads[i];
|
||||
final insertedRead = insertedReads[i];
|
||||
expect(fetchedRead.user.id, insertedRead.user.id);
|
||||
expect(fetchedRead.lastRead, isSameDateAs(insertedRead.lastRead));
|
||||
expect(fetchedRead.unreadMessages, insertedRead.unreadMessages);
|
||||
}
|
||||
});
|
||||
|
||||
test('updateReads', () async {
|
||||
const cid = 'testCid';
|
||||
|
||||
// Preparing test data
|
||||
final insertedReads = await _prepareReadData(cid);
|
||||
|
||||
// Modifying one of the read and also adding one new
|
||||
final copyRead = insertedReads.first.copyWith(unreadMessages: 33);
|
||||
final newUser = User(id: 'testUserId3');
|
||||
final newRead = Read(
|
||||
lastRead: DateTime.now(),
|
||||
user: newUser,
|
||||
unreadMessages: 30,
|
||||
);
|
||||
await database.userDao.updateUsers([newUser]);
|
||||
await readDao.updateReads(cid, [copyRead, newRead]);
|
||||
|
||||
// Fetched reads length should be one more than inserted reads.
|
||||
// copyRead `unreadMessages` modified field should be 33.
|
||||
// Fetched reads should contain the newRead.
|
||||
final fetchedReads = await readDao.getReadsByCid(cid);
|
||||
expect(fetchedReads.length, insertedReads.length + 1);
|
||||
expect(
|
||||
fetchedReads
|
||||
.firstWhere((it) => it.user.id == copyRead.user.id)
|
||||
.unreadMessages,
|
||||
33,
|
||||
);
|
||||
expect(
|
||||
fetchedReads
|
||||
.where((it) =>
|
||||
it.user.id == newRead.user.id &&
|
||||
it.unreadMessages == newRead.unreadMessages)
|
||||
.isNotEmpty,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:stream_chat_persistence/src/dao/dao.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
void main() {
|
||||
UserDao userDao;
|
||||
MoorChatDatabase database;
|
||||
|
||||
setUp(() {
|
||||
database = MoorChatDatabase.testable('testUserId');
|
||||
userDao = database.userDao;
|
||||
});
|
||||
|
||||
Future<List<User>> _prepareUserData({int count = 3}) async {
|
||||
final users = List.generate(
|
||||
count,
|
||||
(index) => User(
|
||||
id: 'testUserId$index',
|
||||
role: 'testRole',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
lastActive: DateTime.now(),
|
||||
online: math.Random().nextBool(),
|
||||
banned: math.Random().nextBool(),
|
||||
),
|
||||
);
|
||||
await userDao.updateUsers(users);
|
||||
return users;
|
||||
}
|
||||
|
||||
test('updateUsers', () async {
|
||||
// Preparing test data
|
||||
final insertedUsers = await _prepareUserData();
|
||||
|
||||
// Modifying one of the user and also adding one new
|
||||
final copyUser = insertedUsers.first.copyWith(online: false);
|
||||
final newUser = User(
|
||||
id: 'testUserId3',
|
||||
role: 'testRole',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
lastActive: DateTime.now(),
|
||||
online: math.Random().nextBool(),
|
||||
banned: math.Random().nextBool(),
|
||||
);
|
||||
await userDao.updateUsers([copyUser, newUser]);
|
||||
|
||||
// Fetched users length should be one more than inserted users.
|
||||
// copyUser `online` modified field should be `false`.
|
||||
// Fetched users should contain the newUser.
|
||||
final fetchedUsers = await userDao.getUsers();
|
||||
expect(fetchedUsers.length, insertedUsers.length + 1);
|
||||
expect(fetchedUsers.firstWhere((it) => it.id == copyUser.id).online, false);
|
||||
expect(fetchedUsers.contains(newUser), true);
|
||||
});
|
||||
|
||||
test('getUsers', () async {
|
||||
// Should be empty initially
|
||||
final users = await userDao.getUsers();
|
||||
expect(users, isEmpty);
|
||||
|
||||
// Preparing test data
|
||||
final insertedUsers = await _prepareUserData();
|
||||
expect(insertedUsers, isNotEmpty);
|
||||
|
||||
// Fetched user list should match inserted user list length
|
||||
final fetchedUsers = await userDao.getUsers();
|
||||
expect(fetchedUsers.length, insertedUsers.length);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart' hide isNotNull;
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
DatabaseConnection _backgroundConnection() =>
|
||||
DatabaseConnection.fromExecutor(VmDatabase.memory());
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'default constructor should create a new instance of MoorChatDatabase',
|
||||
() async {
|
||||
const userId = 'testUserId';
|
||||
final executor = VmDatabase.memory();
|
||||
final database = MoorChatDatabase(userId, executor);
|
||||
expect(database, isNotNull);
|
||||
expect(database.userId, userId);
|
||||
|
||||
addTearDown(() async {
|
||||
await database.disconnect();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'connect constructor should create a new instance of MoorChatDatabase',
|
||||
() async {
|
||||
const userId = 'testUserId';
|
||||
final isolate = await MoorIsolate.spawn(_backgroundConnection);
|
||||
final connection = DatabaseConnection.delayed(isolate.connect());
|
||||
|
||||
final database = MoorChatDatabase.connect(userId, connection);
|
||||
expect(database, isNotNull);
|
||||
expect(database.userId, userId);
|
||||
|
||||
addTearDown(() async {
|
||||
await database.disconnect();
|
||||
await isolate.shutdownAll();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
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/channel_mapper.dart';
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
group('ChannelEntity', () {
|
||||
final user = User(id: 'testUserId');
|
||||
final entity = ChannelEntity(
|
||||
id: 'testId',
|
||||
type: 'testType',
|
||||
cid: 'testCid',
|
||||
config: {'max_message_length': 33},
|
||||
frozen: math.Random().nextBool(),
|
||||
lastMessageAt: DateTime.now(),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
deletedAt: DateTime.now(),
|
||||
memberCount: 33,
|
||||
createdById: user.id,
|
||||
extraData: {'test_extra_data': 'testData'},
|
||||
);
|
||||
|
||||
test('toChannelModel should map entity into ChannelModel', () {
|
||||
final channelModel = entity.toChannelModel(createdBy: user);
|
||||
expect(channelModel, isA<ChannelModel>());
|
||||
expect(channelModel.id, entity.id);
|
||||
expect(channelModel.config.toJson()['max_message_length'], 33);
|
||||
expect(channelModel.frozen, entity.frozen);
|
||||
expect(channelModel.createdAt, isSameDateAs(entity.createdAt));
|
||||
expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt));
|
||||
expect(channelModel.memberCount, entity.memberCount);
|
||||
expect(channelModel.cid, entity.cid);
|
||||
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt));
|
||||
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt));
|
||||
expect(channelModel.extraData, entity.extraData);
|
||||
expect(channelModel.createdBy.id, entity.createdById);
|
||||
});
|
||||
|
||||
test('toChannelState should map entity into ChannelState ', () {
|
||||
final members = List.generate(3, (index) => Member());
|
||||
final reads = List.generate(3, (index) => Read());
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
|
||||
final channelState = entity.toChannelState(
|
||||
createdBy: user,
|
||||
members: members,
|
||||
reads: reads,
|
||||
messages: messages,
|
||||
pinnedMessages: messages,
|
||||
);
|
||||
|
||||
expect(channelState, isA<ChannelState>());
|
||||
expect(channelState.members.length, members.length);
|
||||
expect(channelState.read.length, reads.length);
|
||||
expect(channelState.messages.length, messages.length);
|
||||
expect(channelState.pinnedMessages.length, messages.length);
|
||||
|
||||
final channelModel = channelState.channel;
|
||||
expect(channelModel.id, entity.id);
|
||||
expect(channelModel.config.toJson()['max_message_length'], 33);
|
||||
expect(channelModel.frozen, entity.frozen);
|
||||
expect(channelModel.createdAt, isSameDateAs(entity.createdAt));
|
||||
expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt));
|
||||
expect(channelModel.memberCount, entity.memberCount);
|
||||
expect(channelModel.cid, entity.cid);
|
||||
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt));
|
||||
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt));
|
||||
expect(channelModel.extraData, entity.extraData);
|
||||
expect(channelModel.createdBy.id, entity.createdById);
|
||||
});
|
||||
});
|
||||
|
||||
test('toEntity should map model into ChannelEntity', () {
|
||||
final createdBy = User(id: 'testUserId');
|
||||
final model = ChannelModel(
|
||||
id: 'testId',
|
||||
type: 'testType',
|
||||
cid: 'testCid',
|
||||
config: ChannelConfig(maxMessageLength: 33),
|
||||
frozen: math.Random().nextBool(),
|
||||
lastMessageAt: DateTime.now(),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
deletedAt: DateTime.now(),
|
||||
memberCount: 33,
|
||||
createdBy: createdBy,
|
||||
extraData: {'test_extra_data': 'testData'},
|
||||
);
|
||||
|
||||
final channelEntity = model.toEntity();
|
||||
expect(channelEntity, isA<ChannelEntity>());
|
||||
expect(channelEntity.id, model.id);
|
||||
expect(
|
||||
channelEntity.config['max_message_length'],
|
||||
model.config.maxMessageLength,
|
||||
);
|
||||
expect(channelEntity.frozen, model.frozen);
|
||||
expect(channelEntity.createdAt, isSameDateAs(model.createdAt));
|
||||
expect(channelEntity.updatedAt, isSameDateAs(model.updatedAt));
|
||||
expect(channelEntity.memberCount, model.memberCount);
|
||||
expect(channelEntity.cid, model.cid);
|
||||
expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt));
|
||||
expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt));
|
||||
expect(channelEntity.extraData, model.extraData);
|
||||
expect(channelEntity.createdById, model.createdBy.id);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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/event_mapper.dart';
|
||||
|
||||
void main() {
|
||||
test('toEvent should map entity into Event', () {
|
||||
final ownUser = OwnUser(id: 'testUserId');
|
||||
final entity = ConnectionEventEntity(
|
||||
id: 3,
|
||||
ownUser: ownUser.toJson(),
|
||||
totalUnreadCount: 33,
|
||||
unreadChannels: 33,
|
||||
lastSyncAt: DateTime.now(),
|
||||
lastEventAt: DateTime.now(),
|
||||
);
|
||||
final event = entity.toEvent();
|
||||
expect(event, isA<Event>());
|
||||
expect(event.me.id, ownUser.id);
|
||||
expect(event.totalUnreadCount, entity.totalUnreadCount);
|
||||
expect(event.unreadChannels, entity.unreadChannels);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
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/member_mapper.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
test('toMember should map entity into Member', () {
|
||||
final user = User(id: 'testUserId');
|
||||
final entity = MemberEntity(
|
||||
userId: user.id,
|
||||
channelCid: 'testCid',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
role: 'testRole',
|
||||
inviteAcceptedAt: DateTime.now(),
|
||||
inviteRejectedAt: DateTime.now(),
|
||||
invited: math.Random().nextBool(),
|
||||
banned: math.Random().nextBool(),
|
||||
shadowBanned: math.Random().nextBool(),
|
||||
isModerator: math.Random().nextBool(),
|
||||
);
|
||||
final member = entity.toMember(user: user);
|
||||
expect(member, isA<Member>());
|
||||
expect(member.user.id, entity.userId);
|
||||
expect(member.createdAt, isSameDateAs(entity.createdAt));
|
||||
expect(member.updatedAt, isSameDateAs(entity.updatedAt));
|
||||
expect(member.role, entity.role);
|
||||
expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt));
|
||||
expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt));
|
||||
expect(member.invited, entity.invited);
|
||||
expect(member.banned, entity.banned);
|
||||
expect(member.shadowBanned, entity.shadowBanned);
|
||||
expect(member.isModerator, entity.isModerator);
|
||||
});
|
||||
|
||||
test('toEntity show map member into MemberEntity', () {
|
||||
const cid = 'testCid';
|
||||
final user = User(id: 'testUserId');
|
||||
final member = Member(
|
||||
user: user,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
role: 'testRole',
|
||||
inviteAcceptedAt: DateTime.now(),
|
||||
inviteRejectedAt: DateTime.now(),
|
||||
invited: math.Random().nextBool(),
|
||||
banned: math.Random().nextBool(),
|
||||
shadowBanned: math.Random().nextBool(),
|
||||
isModerator: math.Random().nextBool(),
|
||||
);
|
||||
final entity = member.toEntity(cid: cid);
|
||||
expect(entity, isA<MemberEntity>());
|
||||
expect(entity.channelCid, cid);
|
||||
expect(entity.userId, member.user.id);
|
||||
expect(entity.createdAt, isSameDateAs(member.createdAt));
|
||||
expect(entity.updatedAt, isSameDateAs(member.updatedAt));
|
||||
expect(entity.role, member.role);
|
||||
expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt));
|
||||
expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt));
|
||||
expect(entity.invited, member.invited);
|
||||
expect(entity.banned, member.banned);
|
||||
expect(entity.shadowBanned, member.shadowBanned);
|
||||
expect(entity.isModerator, member.isModerator);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
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/message_mapper.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
test('toMessage should map the entity into Message', () {
|
||||
final user = User(id: 'testUserId');
|
||||
final quotedMessage = Message(id: 'testQuotedMessageId');
|
||||
final reactions = List.generate(
|
||||
3,
|
||||
(index) => Reaction(
|
||||
messageId: 'testMessageId',
|
||||
createdAt: DateTime.now(),
|
||||
type: 'testType$index',
|
||||
user: user,
|
||||
score: math.Random().nextInt(50),
|
||||
),
|
||||
);
|
||||
final attachments = List.generate(
|
||||
3,
|
||||
(index) => Attachment(
|
||||
id: 'testAttachmentId',
|
||||
type: 'testAttachmentType',
|
||||
assetUrl: 'testAssetUrl',
|
||||
),
|
||||
);
|
||||
final entity = MessageEntity(
|
||||
id: 'testMessageId',
|
||||
attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
|
||||
channelCid: 'testCid',
|
||||
type: 'testType',
|
||||
parentId: 'testParentId',
|
||||
quotedMessageId: quotedMessage.id,
|
||||
command: 'testCommand',
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
showInChannel: math.Random().nextBool(),
|
||||
replyCount: 33,
|
||||
reactionScores: {for (final r in reactions) r.type: r.score},
|
||||
reactionCounts: reactions.fold(
|
||||
{},
|
||||
(prev, curr) =>
|
||||
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
|
||||
),
|
||||
status: MessageSendingStatus.sent,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_data': 'extraData'},
|
||||
userId: user.id,
|
||||
deletedAt: DateTime.now(),
|
||||
messageText: 'dummy text',
|
||||
pinned: true,
|
||||
pinExpires: DateTime.now().toUtc(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedByUserId: user.id,
|
||||
);
|
||||
final message = entity.toMessage(
|
||||
user: user,
|
||||
pinnedBy: user,
|
||||
latestReactions: reactions,
|
||||
ownReactions: reactions,
|
||||
quotedMessage: quotedMessage,
|
||||
);
|
||||
|
||||
expect(message, isA<Message>());
|
||||
expect(message.id, entity.id);
|
||||
expect(message.type, entity.type);
|
||||
expect(message.parentId, entity.parentId);
|
||||
expect(message.quotedMessageId, entity.quotedMessageId);
|
||||
expect(message.command, entity.command);
|
||||
expect(message.createdAt, isSameDateAs(entity.createdAt));
|
||||
expect(message.shadowed, entity.shadowed);
|
||||
expect(message.showInChannel, entity.showInChannel);
|
||||
expect(message.replyCount, entity.replyCount);
|
||||
expect(message.reactionScores, entity.reactionScores);
|
||||
expect(message.reactionCounts, entity.reactionCounts);
|
||||
expect(message.status, entity.status);
|
||||
expect(message.updatedAt, isSameDateAs(entity.updatedAt));
|
||||
expect(message.extraData, entity.extraData);
|
||||
expect(message.user.id, entity.userId);
|
||||
expect(message.deletedAt, isSameDateAs(entity.deletedAt));
|
||||
expect(message.text, entity.messageText);
|
||||
expect(message.pinned, entity.pinned);
|
||||
expect(message.pinExpires, isSameDateAs(entity.pinExpires));
|
||||
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt));
|
||||
expect(message.pinnedBy.id, entity.pinnedByUserId);
|
||||
expect(message.reactionCounts, entity.reactionCounts);
|
||||
expect(message.reactionScores, entity.reactionScores);
|
||||
for (var i = 0; i < message.attachments.length; i++) {
|
||||
final messageAttachment = message.attachments[i];
|
||||
final entityAttachmentData = jsonDecode(entity.attachments[i]);
|
||||
final entityAttachment = Attachment.fromData(entityAttachmentData);
|
||||
expect(messageAttachment.id, entityAttachment.id);
|
||||
expect(messageAttachment.type, entityAttachment.type);
|
||||
expect(messageAttachment.assetUrl, entityAttachment.assetUrl);
|
||||
}
|
||||
});
|
||||
|
||||
test('toEntity should map message into MessageEntity', () {
|
||||
const cid = 'testCid';
|
||||
final user = User(id: 'testUserId');
|
||||
final quotedMessage = Message(id: 'testQuotedMessageId');
|
||||
final reactions = List.generate(
|
||||
3,
|
||||
(index) => Reaction(
|
||||
messageId: 'testMessageId',
|
||||
createdAt: DateTime.now(),
|
||||
type: 'testType$index',
|
||||
user: user,
|
||||
score: math.Random().nextInt(50),
|
||||
),
|
||||
);
|
||||
final attachments = List.generate(
|
||||
3,
|
||||
(index) => Attachment(
|
||||
id: 'testAttachmentId',
|
||||
type: 'testAttachmentType',
|
||||
assetUrl: 'testAssetUrl',
|
||||
),
|
||||
);
|
||||
final message = Message(
|
||||
id: 'testMessageId',
|
||||
attachments: attachments,
|
||||
type: 'testType',
|
||||
parentId: 'testParentId',
|
||||
quotedMessageId: quotedMessage.id,
|
||||
command: 'testCommand',
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
showInChannel: math.Random().nextBool(),
|
||||
replyCount: 33,
|
||||
reactionScores: {for (final r in reactions) r.type: r.score},
|
||||
reactionCounts: reactions.fold(
|
||||
{},
|
||||
(prev, curr) =>
|
||||
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
|
||||
),
|
||||
status: MessageSendingStatus.sending,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_data': 'extraData'},
|
||||
user: user,
|
||||
deletedAt: DateTime.now(),
|
||||
text: 'dummy text',
|
||||
pinned: true,
|
||||
pinExpires: DateTime.now(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: user,
|
||||
);
|
||||
final entity = message.toEntity(cid: cid);
|
||||
expect(entity, isA<MessageEntity>());
|
||||
expect(entity.id, message.id);
|
||||
expect(entity.type, message.type);
|
||||
expect(entity.parentId, message.parentId);
|
||||
expect(entity.quotedMessageId, message.quotedMessageId);
|
||||
expect(entity.command, message.command);
|
||||
expect(entity.createdAt, isSameDateAs(message.createdAt));
|
||||
expect(entity.shadowed, message.shadowed);
|
||||
expect(entity.showInChannel, message.showInChannel);
|
||||
expect(entity.replyCount, message.replyCount);
|
||||
expect(entity.reactionScores, message.reactionScores);
|
||||
expect(entity.reactionCounts, message.reactionCounts);
|
||||
expect(entity.status, message.status);
|
||||
expect(entity.updatedAt, isSameDateAs(message.updatedAt));
|
||||
expect(entity.extraData, message.extraData);
|
||||
expect(entity.userId, message.user.id);
|
||||
expect(entity.deletedAt, isSameDateAs(message.deletedAt));
|
||||
expect(entity.messageText, message.text);
|
||||
expect(entity.pinned, message.pinned);
|
||||
expect(entity.pinExpires, isSameDateAs(message.pinExpires));
|
||||
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt));
|
||||
expect(entity.pinnedByUserId, message.pinnedBy.id);
|
||||
expect(entity.reactionCounts, message.reactionCounts);
|
||||
expect(entity.reactionScores, message.reactionScores);
|
||||
expect(
|
||||
entity.attachments,
|
||||
message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
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_mapper.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
test('toMessage should map the entity into Message', () {
|
||||
final user = User(id: 'testUserId');
|
||||
final quotedMessage = Message(id: 'testQuotedMessageId');
|
||||
final reactions = List.generate(
|
||||
3,
|
||||
(index) => Reaction(
|
||||
messageId: 'testMessageId',
|
||||
createdAt: DateTime.now(),
|
||||
type: 'testType$index',
|
||||
user: user,
|
||||
score: math.Random().nextInt(50),
|
||||
),
|
||||
);
|
||||
final attachments = List.generate(
|
||||
3,
|
||||
(index) => Attachment(
|
||||
id: 'testAttachmentId',
|
||||
type: 'testAttachmentType',
|
||||
assetUrl: 'testAssetUrl',
|
||||
),
|
||||
);
|
||||
final entity = PinnedMessageEntity(
|
||||
id: 'testMessageId',
|
||||
attachments: attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
|
||||
channelCid: 'testCid',
|
||||
type: 'testType',
|
||||
parentId: 'testParentId',
|
||||
quotedMessageId: quotedMessage.id,
|
||||
command: 'testCommand',
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
showInChannel: math.Random().nextBool(),
|
||||
replyCount: 33,
|
||||
reactionScores: {for (final r in reactions) r.type: r.score},
|
||||
reactionCounts: reactions.fold(
|
||||
{},
|
||||
(prev, curr) =>
|
||||
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
|
||||
),
|
||||
status: MessageSendingStatus.sent,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_data': 'extraData'},
|
||||
userId: user.id,
|
||||
deletedAt: DateTime.now(),
|
||||
messageText: 'dummy text',
|
||||
pinned: true,
|
||||
pinExpires: DateTime.now().toUtc(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedByUserId: user.id,
|
||||
);
|
||||
final message = entity.toMessage(
|
||||
user: user,
|
||||
pinnedBy: user,
|
||||
latestReactions: reactions,
|
||||
ownReactions: reactions,
|
||||
quotedMessage: quotedMessage,
|
||||
);
|
||||
|
||||
expect(message, isA<Message>());
|
||||
expect(message.id, entity.id);
|
||||
expect(message.type, entity.type);
|
||||
expect(message.parentId, entity.parentId);
|
||||
expect(message.quotedMessageId, entity.quotedMessageId);
|
||||
expect(message.command, entity.command);
|
||||
expect(message.createdAt, isSameDateAs(entity.createdAt));
|
||||
expect(message.shadowed, entity.shadowed);
|
||||
expect(message.showInChannel, entity.showInChannel);
|
||||
expect(message.replyCount, entity.replyCount);
|
||||
expect(message.reactionScores, entity.reactionScores);
|
||||
expect(message.reactionCounts, entity.reactionCounts);
|
||||
expect(message.status, entity.status);
|
||||
expect(message.updatedAt, isSameDateAs(entity.updatedAt));
|
||||
expect(message.extraData, entity.extraData);
|
||||
expect(message.user.id, entity.userId);
|
||||
expect(message.deletedAt, isSameDateAs(entity.deletedAt));
|
||||
expect(message.text, entity.messageText);
|
||||
expect(message.pinned, entity.pinned);
|
||||
expect(message.pinExpires, isSameDateAs(entity.pinExpires));
|
||||
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt));
|
||||
expect(message.pinnedBy.id, entity.pinnedByUserId);
|
||||
expect(message.reactionCounts, entity.reactionCounts);
|
||||
expect(message.reactionScores, entity.reactionScores);
|
||||
for (var i = 0; i < message.attachments.length; i++) {
|
||||
final messageAttachment = message.attachments[i];
|
||||
final entityAttachmentData = jsonDecode(entity.attachments[i]);
|
||||
final entityAttachment = Attachment.fromData(entityAttachmentData);
|
||||
expect(messageAttachment.id, entityAttachment.id);
|
||||
expect(messageAttachment.type, entityAttachment.type);
|
||||
expect(messageAttachment.assetUrl, entityAttachment.assetUrl);
|
||||
}
|
||||
});
|
||||
|
||||
test('toPinnedEntity should map message into PinnedMessageEntity', () {
|
||||
const cid = 'testCid';
|
||||
final user = User(id: 'testUserId');
|
||||
final quotedMessage = Message(id: 'testQuotedMessageId');
|
||||
final reactions = List.generate(
|
||||
3,
|
||||
(index) => Reaction(
|
||||
messageId: 'testMessageId',
|
||||
createdAt: DateTime.now(),
|
||||
type: 'testType$index',
|
||||
user: user,
|
||||
score: math.Random().nextInt(50),
|
||||
),
|
||||
);
|
||||
final attachments = List.generate(
|
||||
3,
|
||||
(index) => Attachment(
|
||||
id: 'testAttachmentId',
|
||||
type: 'testAttachmentType',
|
||||
assetUrl: 'testAssetUrl',
|
||||
),
|
||||
);
|
||||
final message = Message(
|
||||
id: 'testMessageId',
|
||||
attachments: attachments,
|
||||
type: 'testType',
|
||||
parentId: 'testParentId',
|
||||
quotedMessageId: quotedMessage.id,
|
||||
command: 'testCommand',
|
||||
createdAt: DateTime.now(),
|
||||
shadowed: math.Random().nextBool(),
|
||||
showInChannel: math.Random().nextBool(),
|
||||
replyCount: 33,
|
||||
reactionScores: {for (final r in reactions) r.type: r.score},
|
||||
reactionCounts: reactions.fold(
|
||||
{},
|
||||
(prev, curr) =>
|
||||
prev..update(curr.type, (value) => value + 1, ifAbsent: () => 1),
|
||||
),
|
||||
status: MessageSendingStatus.sending,
|
||||
updatedAt: DateTime.now(),
|
||||
extraData: {'extra_test_data': 'extraData'},
|
||||
user: user,
|
||||
deletedAt: DateTime.now(),
|
||||
text: 'dummy text',
|
||||
pinned: true,
|
||||
pinExpires: DateTime.now(),
|
||||
pinnedAt: DateTime.now(),
|
||||
pinnedBy: user,
|
||||
);
|
||||
final entity = message.toPinnedEntity(cid: cid);
|
||||
expect(entity, isA<PinnedMessageEntity>());
|
||||
expect(entity.id, message.id);
|
||||
expect(entity.type, message.type);
|
||||
expect(entity.parentId, message.parentId);
|
||||
expect(entity.quotedMessageId, message.quotedMessageId);
|
||||
expect(entity.command, message.command);
|
||||
expect(entity.createdAt, isSameDateAs(message.createdAt));
|
||||
expect(entity.shadowed, message.shadowed);
|
||||
expect(entity.showInChannel, message.showInChannel);
|
||||
expect(entity.replyCount, message.replyCount);
|
||||
expect(entity.reactionScores, message.reactionScores);
|
||||
expect(entity.reactionCounts, message.reactionCounts);
|
||||
expect(entity.status, message.status);
|
||||
expect(entity.updatedAt, isSameDateAs(message.updatedAt));
|
||||
expect(entity.extraData, message.extraData);
|
||||
expect(entity.userId, message.user.id);
|
||||
expect(entity.deletedAt, isSameDateAs(message.deletedAt));
|
||||
expect(entity.messageText, message.text);
|
||||
expect(entity.pinned, message.pinned);
|
||||
expect(entity.pinExpires, isSameDateAs(message.pinExpires));
|
||||
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt));
|
||||
expect(entity.pinnedByUserId, message.pinnedBy.id);
|
||||
expect(entity.reactionCounts, message.reactionCounts);
|
||||
expect(entity.reactionScores, message.reactionScores);
|
||||
expect(
|
||||
entity.attachments,
|
||||
message.attachments?.map((it) => jsonEncode(it.toData()))?.toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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/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 = ReactionEntity(
|
||||
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 ReactionEntity', () {
|
||||
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.toEntity();
|
||||
expect(entity, isA<ReactionEntity>());
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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/read_mapper.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
test('toRead should map entity into Read', () {
|
||||
const cid = 'testCid';
|
||||
final user = User(id: 'testUserId');
|
||||
final entity = ReadEntity(
|
||||
lastRead: DateTime.now(),
|
||||
userId: user.id,
|
||||
channelCid: cid,
|
||||
unreadMessages: 33,
|
||||
);
|
||||
|
||||
final read = entity.toRead(user: user);
|
||||
expect(read, isA<Read>());
|
||||
expect(read.lastRead, isSameDateAs(entity.lastRead));
|
||||
expect(read.user.id, entity.userId);
|
||||
expect(read.unreadMessages, entity.unreadMessages);
|
||||
});
|
||||
|
||||
test('toEntity should map read into ReadEntity', () {
|
||||
const cid = 'testCid';
|
||||
final user = User(id: 'testUserId');
|
||||
final read = Read(
|
||||
lastRead: DateTime.now(),
|
||||
user: user,
|
||||
unreadMessages: 33,
|
||||
);
|
||||
|
||||
final entity = read.toEntity(cid: cid);
|
||||
expect(entity, isA<ReadEntity>());
|
||||
expect(entity.lastRead, isSameDateAs(read.lastRead));
|
||||
expect(entity.userId, read.user.id);
|
||||
expect(entity.unreadMessages, read.unreadMessages);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'dart:math' as math;
|
||||
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/user_mapper.dart';
|
||||
|
||||
import '../utils/date_matcher.dart';
|
||||
|
||||
void main() {
|
||||
test('toUser should map entity into User', () {
|
||||
final entity = UserEntity(
|
||||
id: 'testUserId',
|
||||
role: 'testType',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
lastActive: DateTime.now(),
|
||||
online: math.Random().nextBool(),
|
||||
banned: math.Random().nextBool(),
|
||||
extraData: {'test_extra_data': 'extraData'},
|
||||
);
|
||||
final user = entity.toUser();
|
||||
expect(user, isA<User>());
|
||||
expect(user.id, entity.id);
|
||||
expect(user.role, entity.role);
|
||||
expect(user.createdAt, isSameDateAs(entity.createdAt));
|
||||
expect(user.updatedAt, isSameDateAs(entity.updatedAt));
|
||||
expect(user.lastActive, isSameDateAs(entity.lastActive));
|
||||
expect(user.online, entity.online);
|
||||
expect(user.banned, entity.banned);
|
||||
expect(user.extraData, entity.extraData);
|
||||
});
|
||||
|
||||
test('toEntity should map user into UserEntity', () {
|
||||
final user = User(
|
||||
id: 'testUserId',
|
||||
role: 'testType',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
lastActive: DateTime.now(),
|
||||
online: math.Random().nextBool(),
|
||||
banned: math.Random().nextBool(),
|
||||
extraData: {'test_extra_data': 'extraData'},
|
||||
);
|
||||
final entity = user.toEntity();
|
||||
expect(entity, isA<UserEntity>());
|
||||
expect(entity.id, user.id);
|
||||
expect(entity.role, user.role);
|
||||
expect(entity.createdAt, isSameDateAs(user.createdAt));
|
||||
expect(entity.updatedAt, isSameDateAs(user.updatedAt));
|
||||
expect(entity.lastActive, isSameDateAs(user.lastActive));
|
||||
expect(entity.online, user.online);
|
||||
expect(entity.banned, user.banned);
|
||||
expect(entity.extraData, user.extraData);
|
||||
});
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('connect', () {
|
||||
test('throws exception because already connected', () {
|
||||
final streamChatPersistenceClient = StreamChatPersistenceClient(
|
||||
connectionMode: ConnectionMode.background,
|
||||
logLevel: Level.INFO,
|
||||
)..db = MoorChatDatabase(
|
||||
'test',
|
||||
persistOnDisk: false,
|
||||
);
|
||||
|
||||
expect(
|
||||
() => streamChatPersistenceClient.connect('test'),
|
||||
throwsA(allOf(isException, predicate((e) {
|
||||
return e.message ==
|
||||
'An instance of StreamChatDatabase is already connected.\n'
|
||||
'disconnect the previous instance before connecting again.';
|
||||
}))),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
Matcher isSameDateAs(DateTime targetDate) =>
|
||||
_IsSameDateAs(targetDate: targetDate);
|
||||
|
||||
class _IsSameDateAs extends Matcher {
|
||||
const _IsSameDateAs({
|
||||
@required this.targetDate,
|
||||
}) : assert(targetDate != null, '');
|
||||
|
||||
final DateTime targetDate;
|
||||
|
||||
@override
|
||||
bool matches(covariant DateTime date, Map matchState) =>
|
||||
date.year == targetDate.year &&
|
||||
date.month == targetDate.month &&
|
||||
date.day == targetDate.day &&
|
||||
date.hour == targetDate.hour &&
|
||||
date.minute == targetDate.minute &&
|
||||
date.second == targetDate.second;
|
||||
|
||||
@override
|
||||
Description describe(Description description) =>
|
||||
description.add('is same date as $targetDate');
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
import 'package:mocktail/mocktail.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/stream_chat_persistence_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'mock_chat_database.dart';
|
||||
import 'src/utils/date_matcher.dart';
|
||||
|
||||
MoorChatDatabase _testDatabaseProvider(String userId, ConnectionMode mode) =>
|
||||
MoorChatDatabase.testable(userId);
|
||||
|
||||
void main() {
|
||||
group('client constructor', () {
|
||||
test('throws assertion error if null connectionMode is provided', () {
|
||||
expect(
|
||||
() => StreamChatPersistenceClient(connectionMode: null),
|
||||
throwsA(isA<AssertionError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws assertion error if null logLevel is provided', () {
|
||||
expect(
|
||||
() => StreamChatPersistenceClient(logLevel: null),
|
||||
throwsA(isA<AssertionError>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('connect', () {
|
||||
const userId = 'testUserId';
|
||||
test('successfully connects with the Database', () async {
|
||||
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
|
||||
expect(client.db, isNull);
|
||||
await client.connect(userId, databaseProvider: _testDatabaseProvider);
|
||||
expect(client.db, isNotNull);
|
||||
expect(client.db, isA<MoorChatDatabase>());
|
||||
expect(client.db.userId, userId);
|
||||
|
||||
addTearDown(() async {
|
||||
await client.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
test('throws if already connected', () async {
|
||||
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
|
||||
expect(client.db, isNull);
|
||||
await client.connect(userId, databaseProvider: _testDatabaseProvider);
|
||||
expect(client.db, isNotNull);
|
||||
expect(client.db, isNotNull);
|
||||
expect(client.db, isA<MoorChatDatabase>());
|
||||
expect(client.db.userId, userId);
|
||||
expect(
|
||||
() => client.connect(userId, databaseProvider: _testDatabaseProvider),
|
||||
throwsException,
|
||||
);
|
||||
|
||||
addTearDown(() async {
|
||||
await client.disconnect();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('disconnect', () async {
|
||||
const userId = 'testUserId';
|
||||
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
|
||||
await client.connect(userId, databaseProvider: _testDatabaseProvider);
|
||||
expect(client.db, isNotNull);
|
||||
await client.disconnect(flush: true);
|
||||
expect(client.db, isNull);
|
||||
});
|
||||
|
||||
group('client functions', () {
|
||||
const userId = 'testUserId';
|
||||
final mockDatabase = MockChatDatabase();
|
||||
MoorChatDatabase _mockDatabaseProvider(_, __) => mockDatabase;
|
||||
StreamChatPersistenceClient client;
|
||||
|
||||
setUp(() async {
|
||||
client = StreamChatPersistenceClient(logLevel: Level.ALL);
|
||||
await client.connect(userId, databaseProvider: _mockDatabaseProvider);
|
||||
});
|
||||
|
||||
test('getReplies', () async {
|
||||
const parentId = 'testParentId';
|
||||
final replies = List.generate(3, (index) => Message(id: 'testId$index'));
|
||||
|
||||
when(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId))
|
||||
.thenAnswer((_) async => replies);
|
||||
|
||||
final fetchedReplies = await client.getReplies(parentId);
|
||||
expect(fetchedReplies.length, replies.length);
|
||||
verify(() =>
|
||||
mockDatabase.messageDao.getThreadMessagesByParentId(parentId))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getConnectionInfo', () async {
|
||||
final event = Event(type: 'testEvent');
|
||||
when(() => mockDatabase.connectionEventDao.connectionEvent)
|
||||
.thenAnswer((_) async => event);
|
||||
|
||||
final fetchedEvent = await client.getConnectionInfo();
|
||||
expect(fetchedEvent.type, event.type);
|
||||
verify(() => mockDatabase.connectionEventDao.connectionEvent).called(1);
|
||||
});
|
||||
|
||||
test('getLastSyncAt', () async {
|
||||
final lastSync = DateTime.now();
|
||||
when(() => mockDatabase.connectionEventDao.lastSyncAt)
|
||||
.thenAnswer((_) async => lastSync);
|
||||
|
||||
final fetchedLastSync = await client.getLastSyncAt();
|
||||
expect(fetchedLastSync, isSameDateAs(lastSync));
|
||||
verify(() => mockDatabase.connectionEventDao.lastSyncAt).called(1);
|
||||
});
|
||||
|
||||
test('updateConnectionInfo', () async {
|
||||
final event = Event(type: 'testEvent');
|
||||
when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateConnectionInfo(event);
|
||||
verify(() => mockDatabase.connectionEventDao.updateConnectionEvent(event))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('updateLastSyncAt', () async {
|
||||
final lastSync = DateTime.now();
|
||||
when(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync))
|
||||
.thenAnswer((_) {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateLastSyncAt(lastSync);
|
||||
verify(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getChannelCids', () async {
|
||||
final channelCids = List.generate(3, (index) => 'testCid$index');
|
||||
when(() => mockDatabase.channelDao.cids)
|
||||
.thenAnswer((_) async => channelCids);
|
||||
|
||||
final fetchedChannelCids = await client.getChannelCids();
|
||||
expect(fetchedChannelCids.length, channelCids.length);
|
||||
verify(() => mockDatabase.channelDao.cids).called(1);
|
||||
});
|
||||
|
||||
test('getChannelByCid', () async {
|
||||
const cid = 'testCid';
|
||||
final channelModel = ChannelModel(cid: cid);
|
||||
when(() => mockDatabase.channelDao.getChannelByCid(cid))
|
||||
.thenAnswer((_) async => channelModel);
|
||||
|
||||
final fetchedChannelModel = await client.getChannelByCid(cid);
|
||||
expect(fetchedChannelModel.cid, channelModel.cid);
|
||||
verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(1);
|
||||
});
|
||||
|
||||
test('getMembersByCid', () async {
|
||||
const cid = 'testCid';
|
||||
final members = List.generate(3, (index) => Member());
|
||||
when(() => mockDatabase.memberDao.getMembersByCid(cid))
|
||||
.thenAnswer((_) async => members);
|
||||
|
||||
final fetchedMembers = await client.getMembersByCid(cid);
|
||||
expect(fetchedMembers.length, members.length);
|
||||
verify(() => mockDatabase.memberDao.getMembersByCid(cid)).called(1);
|
||||
});
|
||||
|
||||
test('getReadsByCid', () async {
|
||||
const cid = 'testCid';
|
||||
final reads = List.generate(3, (index) => Read());
|
||||
when(() => mockDatabase.readDao.getReadsByCid(cid))
|
||||
.thenAnswer((_) async => reads);
|
||||
|
||||
final fetchedReads = await client.getReadsByCid(cid);
|
||||
expect(fetchedReads.length, reads.length);
|
||||
verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(1);
|
||||
});
|
||||
|
||||
test('getMessagesByCid', () async {
|
||||
const cid = 'testCid';
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
when(() => mockDatabase.messageDao.getMessagesByCid(cid))
|
||||
.thenAnswer((_) async => messages);
|
||||
|
||||
final fetchedMessages = await client.getMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, messages.length);
|
||||
verify(() => mockDatabase.messageDao.getMessagesByCid(cid)).called(1);
|
||||
});
|
||||
|
||||
test('getPinnedMessagesByCid', () async {
|
||||
const cid = 'testCid';
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
|
||||
.thenAnswer((_) async => messages);
|
||||
|
||||
final fetchedMessages = await client.getPinnedMessagesByCid(cid);
|
||||
expect(fetchedMessages.length, messages.length);
|
||||
verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getChannelStateByCid', () async {
|
||||
const cid = 'testCid';
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
final members = List.generate(3, (index) => Member());
|
||||
final reads = List.generate(3, (index) => Read());
|
||||
final channel = ChannelModel(cid: cid);
|
||||
|
||||
when(() => mockDatabase.memberDao.getMembersByCid(cid))
|
||||
.thenAnswer((_) async => members);
|
||||
when(() => mockDatabase.readDao.getReadsByCid(cid))
|
||||
.thenAnswer((_) async => reads);
|
||||
when(() => mockDatabase.channelDao.getChannelByCid(cid))
|
||||
.thenAnswer((_) async => channel);
|
||||
when(() => mockDatabase.messageDao.getMessagesByCid(cid))
|
||||
.thenAnswer((_) async => messages);
|
||||
when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
|
||||
.thenAnswer((_) async => messages);
|
||||
|
||||
final fetchedChannelState = await client.getChannelStateByCid(cid);
|
||||
expect(fetchedChannelState.messages.length, messages.length);
|
||||
expect(fetchedChannelState.pinnedMessages.length, messages.length);
|
||||
expect(fetchedChannelState.members.length, members.length);
|
||||
expect(fetchedChannelState.read.length, reads.length);
|
||||
expect(fetchedChannelState.channel.cid, channel.cid);
|
||||
|
||||
verify(() => mockDatabase.memberDao.getMembersByCid(cid)).called(1);
|
||||
verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(1);
|
||||
verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(1);
|
||||
verify(() => mockDatabase.messageDao.getMessagesByCid(cid)).called(1);
|
||||
verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getChannelStates', () async {
|
||||
const cid = 'testCid';
|
||||
final channels = List.generate(3, (index) => ChannelModel(cid: cid));
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
final members = List.generate(3, (index) => Member());
|
||||
final reads = List.generate(3, (index) => Read());
|
||||
final channel = ChannelModel(cid: cid);
|
||||
final channelStates = channels
|
||||
.map(
|
||||
(channel) => ChannelState(
|
||||
channel: channel,
|
||||
messages: messages,
|
||||
pinnedMessages: messages,
|
||||
members: members,
|
||||
read: reads,
|
||||
),
|
||||
)
|
||||
.toList(growable: false);
|
||||
|
||||
when(() => mockDatabase.channelQueryDao.getChannels())
|
||||
.thenAnswer((_) async => channels);
|
||||
when(() => mockDatabase.memberDao.getMembersByCid(cid))
|
||||
.thenAnswer((_) async => members);
|
||||
when(() => mockDatabase.readDao.getReadsByCid(cid))
|
||||
.thenAnswer((_) async => reads);
|
||||
when(() => mockDatabase.channelDao.getChannelByCid(cid))
|
||||
.thenAnswer((_) async => channel);
|
||||
when(() => mockDatabase.messageDao.getMessagesByCid(cid))
|
||||
.thenAnswer((_) async => messages);
|
||||
when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
|
||||
.thenAnswer((_) async => messages);
|
||||
|
||||
final fetchedChannelStates = await client.getChannelStates();
|
||||
expect(fetchedChannelStates.length, channelStates.length);
|
||||
|
||||
for (var i = 0; i < fetchedChannelStates.length; i++) {
|
||||
final original = channelStates[i];
|
||||
final fetched = fetchedChannelStates[i];
|
||||
expect(fetched.members.length, original.members.length);
|
||||
expect(fetched.messages.length, original.messages.length);
|
||||
expect(fetched.pinnedMessages.length, original.pinnedMessages.length);
|
||||
expect(fetched.read.length, original.read.length);
|
||||
expect(fetched.channel.cid, original.channel.cid);
|
||||
}
|
||||
|
||||
verify(() => mockDatabase.channelQueryDao.getChannels()).called(1);
|
||||
verify(() => mockDatabase.memberDao.getMembersByCid(cid)).called(3);
|
||||
verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(3);
|
||||
verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(3);
|
||||
verify(() => mockDatabase.messageDao.getMessagesByCid(cid)).called(3);
|
||||
verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid))
|
||||
.called(3);
|
||||
});
|
||||
|
||||
test('updateChannelQueries', () async {
|
||||
const filter = <String, dynamic>{};
|
||||
const cids = <String>[];
|
||||
when(() =>
|
||||
mockDatabase.channelQueryDao.updateChannelQueries(filter, cids))
|
||||
.thenAnswer((realInvocation) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateChannelQueries(filter, cids);
|
||||
verify(() =>
|
||||
mockDatabase.channelQueryDao.updateChannelQueries(filter, cids))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteMessageById', () async {
|
||||
const messageId = 'testMessageId';
|
||||
when(() => mockDatabase.messageDao.deleteMessageByIds([messageId]))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteMessageById(messageId);
|
||||
verify(() => mockDatabase.messageDao.deleteMessageByIds([messageId]))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deletePinnedMessageById', () async {
|
||||
const messageId = 'testMessageId';
|
||||
when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId]))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deletePinnedMessageById(messageId);
|
||||
verify(() =>
|
||||
mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId]))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteMessageByIds', () async {
|
||||
const messageIds = <String>[];
|
||||
when(() => mockDatabase.messageDao.deleteMessageByIds(messageIds))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteMessageByIds(messageIds);
|
||||
verify(() => mockDatabase.messageDao.deleteMessageByIds(messageIds))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deletePinnedMessageByIds', () async {
|
||||
const messageIds = <String>[];
|
||||
when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deletePinnedMessageByIds(messageIds);
|
||||
verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteMessageByCid', () async {
|
||||
const cid = 'testCid';
|
||||
when(() => mockDatabase.messageDao.deleteMessageByCids([cid]))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteMessageByCid(cid);
|
||||
verify(() => mockDatabase.messageDao.deleteMessageByCids([cid]))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deletePinnedMessageByCid', () async {
|
||||
const cid = 'testCid';
|
||||
when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid]))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deletePinnedMessageByCid(cid);
|
||||
verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid]))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteMessageByCids', () async {
|
||||
const cids = <String>[];
|
||||
when(() => mockDatabase.messageDao.deleteMessageByCids(cids))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteMessageByCids(cids);
|
||||
verify(() => mockDatabase.messageDao.deleteMessageByCids(cids)).called(1);
|
||||
});
|
||||
|
||||
test('deletePinnedMessageByCids', () async {
|
||||
const cids = <String>[];
|
||||
when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deletePinnedMessageByCids(cids);
|
||||
verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteChannels', () async {
|
||||
const cids = <String>[];
|
||||
when(() => mockDatabase.channelDao.deleteChannelByCids(cids))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteChannels(cids);
|
||||
verify(() => mockDatabase.channelDao.deleteChannelByCids(cids)).called(1);
|
||||
});
|
||||
|
||||
test('updateMessages', () async {
|
||||
const cid = 'testCid';
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
when(() => mockDatabase.messageDao.updateMessages(cid, messages))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateMessages(cid, messages);
|
||||
verify(() => mockDatabase.messageDao.updateMessages(cid, messages))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('updatePinnedMessages', () async {
|
||||
const cid = 'testCid';
|
||||
final messages = List.generate(3, (index) => Message());
|
||||
when(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updatePinnedMessages(cid, messages);
|
||||
verify(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('getChannelThreads', () async {
|
||||
const cid = 'testCid';
|
||||
final messages =
|
||||
List.generate(3, (index) => Message(parentId: 'testParentId$index'));
|
||||
final threads = messages.fold<Map<String, List<Message>>>(
|
||||
{},
|
||||
(prev, curr) {
|
||||
return prev
|
||||
..update(
|
||||
curr.parentId,
|
||||
(value) => [...value, curr],
|
||||
ifAbsent: () => [],
|
||||
);
|
||||
},
|
||||
);
|
||||
when(() => mockDatabase.messageDao.getThreadMessages(cid))
|
||||
.thenAnswer((realInvocation) async => messages);
|
||||
|
||||
final fetchedThreads = await client.getChannelThreads(cid);
|
||||
expect(fetchedThreads.length, threads.length);
|
||||
for (var i = 0; i < fetchedThreads.length; i++) {
|
||||
final original = threads.entries.elementAt(i);
|
||||
final fetched = fetchedThreads.entries.elementAt(i);
|
||||
expect(fetched.key, original.key);
|
||||
}
|
||||
|
||||
verify(() => mockDatabase.messageDao.getThreadMessages(cid)).called(1);
|
||||
});
|
||||
|
||||
test('updateChannels', () async {
|
||||
final channels = List.generate(3, (index) => ChannelModel());
|
||||
when(() => mockDatabase.channelDao.updateChannels(channels))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateChannels(channels);
|
||||
verify(() => mockDatabase.channelDao.updateChannels(channels)).called(1);
|
||||
});
|
||||
|
||||
test('updateMembers', () async {
|
||||
const cid = 'testCid';
|
||||
final members = List.generate(3, (index) => Member());
|
||||
when(() => mockDatabase.memberDao.updateMembers(cid, members))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateMembers(cid, members);
|
||||
verify(() => mockDatabase.memberDao.updateMembers(cid, members))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('updateReads', () async {
|
||||
const cid = 'testCid';
|
||||
final reads = List.generate(3, (index) => Read());
|
||||
when(() => mockDatabase.readDao.updateReads(cid, reads))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateReads(cid, reads);
|
||||
verify(() => mockDatabase.readDao.updateReads(cid, reads)).called(1);
|
||||
});
|
||||
|
||||
test('updateUsers', () async {
|
||||
final users = List.generate(3, (index) => User());
|
||||
when(() => mockDatabase.userDao.updateUsers(users)).thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateUsers(users);
|
||||
verify(() => mockDatabase.userDao.updateUsers(users)).called(1);
|
||||
});
|
||||
|
||||
test('updateReactions', () async {
|
||||
final reactions = List.generate(3, (index) => Reaction());
|
||||
when(() => mockDatabase.reactionDao.updateReactions(reactions))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.updateReactions(reactions);
|
||||
verify(() => mockDatabase.reactionDao.updateReactions(reactions))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteReactionsByMessageId', () async {
|
||||
final messageIds = <String>[];
|
||||
when(() =>
|
||||
mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteReactionsByMessageId(messageIds);
|
||||
verify(() =>
|
||||
mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test('deleteMembersByCids', () async {
|
||||
final cids = <String>[];
|
||||
when(() => mockDatabase.memberDao.deleteMemberByCids(cids))
|
||||
.thenAnswer((_) async {
|
||||
return;
|
||||
});
|
||||
|
||||
await client.deleteMembersByCids(cids);
|
||||
verify(() => mockDatabase.memberDao.deleteMemberByCids(cids)).called(1);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await client.disconnect(flush: true);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user