test: add tests for stream chat flutter (#335)

* feat: Added tests

* feat: Added header test

* feat: Added new tests

* fix

* feat: Added test, fmt

* feat: Added delete test

* added system and unread ind tests

* fix: analysis

* added new tests

* fix: analysis

* added tests

* add attachment actions modal tests

* fix analysis

* fix analysis

* add backbutton tests

* fix analysis

* increase timeout

* add channelheader tests

* add infotile tests

* add reactions modal and channel unread indicator tests

* add golden test for reaction bubble

* add dark tests

* add system message golden

* add deletd message golden

* add message action modal tests

* update goldens

* update workflow runner

* update goldens

* remove channel unread indicator in favor of unread indicator

* move file and media screen to sample app

* add channel image tesst

* add channel_list_header test

* add thread_header test

* bump up test threashold

* fix review

Co-authored-by: Salvatore Giordano <salvatoregiordanoo@gmail.com>
This commit is contained in:
Deven Joshi
2021-04-08 21:13:08 +05:30
committed by GitHub
parent 4946664220
commit a42b3de6f4
58 changed files with 4140 additions and 968 deletions
@@ -58,7 +58,7 @@ jobs:
./.github/workflows/scripts/validate-formatting.sh
test:
runs-on: ubuntu-latest
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v2
@@ -102,4 +102,4 @@ jobs:
- uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
with:
path: packages/stream_chat_flutter/coverage/lcov.info
min_coverage: 16
min_coverage: 35
@@ -1,5 +1,6 @@
// ignore_for_file: public_member_api_docs
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/action.dart';
import 'package:stream_chat/src/models/attachment_file.dart';
@@ -10,7 +11,7 @@ part 'attachment.g.dart';
/// The class that contains the information about an attachment
@JsonSerializable(includeIfNull: false)
class Attachment {
class Attachment extends Equatable {
/// Constructor used for json serialization
Attachment({
String id,
@@ -37,12 +38,11 @@ class Attachment {
UploadState uploadState,
}) : id = id ?? Uuid().v4(),
title = title ?? file?.name,
localUri = file?.path != null ? Uri.parse(file.path) : null {
this.uploadState = uploadState ??
((assetUrl != null || imageUrl != null)
? const UploadState.success()
: const UploadState.preparing());
}
localUri = file?.path != null ? Uri.parse(file.path) : null,
uploadState = uploadState ??
((assetUrl != null || imageUrl != null)
? const UploadState.success()
: const UploadState.preparing());
/// Create a new instance from a json
factory Attachment.fromJson(Map<String, dynamic> json) =>
@@ -104,7 +104,7 @@ class Attachment {
final AttachmentFile file;
/// The current upload state of the attachment
UploadState uploadState;
final UploadState uploadState;
/// Map of custom channel extraData
@JsonKey(includeIfNull: false)
@@ -205,49 +205,28 @@ class Attachment {
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Attachment &&
runtimeType == other.runtimeType &&
type == other.type &&
titleLink == other.titleLink &&
title == other.title &&
thumbUrl == other.thumbUrl &&
text == other.text &&
pretext == other.pretext &&
ogScrapeUrl == other.ogScrapeUrl &&
imageUrl == other.imageUrl &&
footerIcon == other.footerIcon &&
footer == other.footer &&
fields == other.fields &&
fallback == other.fallback &&
color == other.color &&
authorName == other.authorName &&
authorLink == other.authorLink &&
authorIcon == other.authorIcon &&
assetUrl == other.assetUrl &&
actions == other.actions &&
extraData == other.extraData;
@override
int get hashCode =>
type.hashCode ^
titleLink.hashCode ^
title.hashCode ^
thumbUrl.hashCode ^
text.hashCode ^
pretext.hashCode ^
ogScrapeUrl.hashCode ^
imageUrl.hashCode ^
footerIcon.hashCode ^
footer.hashCode ^
fields.hashCode ^
fallback.hashCode ^
color.hashCode ^
authorName.hashCode ^
authorLink.hashCode ^
authorIcon.hashCode ^
assetUrl.hashCode ^
actions.hashCode ^
extraData.hashCode;
List<Object> get props => [
id,
type,
titleLink,
title,
thumbUrl,
text,
pretext,
ogScrapeUrl,
imageUrl,
footerIcon,
footer,
fields,
fallback,
color,
authorName,
authorLink,
authorIcon,
assetUrl,
actions,
file,
uploadState,
extraData,
];
}
@@ -1,3 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/models/attachment.dart';
import 'package:stream_chat/src/models/reaction.dart';
@@ -41,7 +42,7 @@ enum MessageSendingStatus {
/// The class that contains the information about a message
@JsonSerializable()
class Message {
class Message extends Equatable {
/// Constructor used for json serialization
Message({
String id,
@@ -345,6 +346,39 @@ class Message {
pinnedBy: other.pinnedBy,
);
}
@override
List<Object> get props => [
id,
text,
type,
attachments,
mentionedUsers,
reactionCounts,
reactionScores,
latestReactions,
ownReactions,
parentId,
quotedMessage,
quotedMessageId,
replyCount,
threadParticipants,
showInChannel,
shadowed,
silent,
command,
createdAt,
updatedAt,
deletedAt,
user,
pinned,
pinnedAt,
pinExpires,
pinnedBy,
extraData,
status,
skipPush,
];
}
/// A translated message
@@ -15,6 +15,7 @@ export './src/attachment_file_uploader.dart' show AttachmentFileUploader;
export './src/client.dart';
export './src/db/chat_persistence_client.dart';
export './src/event_type.dart';
export './src/exceptions.dart';
export './src/extensions/rate_limit.dart';
export './src/extensions/string_extension.dart';
export './src/models/action.dart';
+1
View File
@@ -12,6 +12,7 @@ dependencies:
async: ^2.4.2
collection: ^1.14.13
dio: ^3.0.10
equatable: ^2.0.0
freezed_annotation: ^0.12.0
http_parser: ^3.1.4
json_annotation: ^3.0.1
+4 -1
View File
@@ -62,4 +62,7 @@ doc/api/
fvm
google-services.json
example/ios/dist
.vscode/
.vscode/
# don't check in golden failure output
**/failures/*.png
@@ -34,12 +34,12 @@ class AttachmentActionsModal extends StatelessWidget {
/// Returns a new [AttachmentActionsModal]
const AttachmentActionsModal({
@required this.currentIndex,
this.message,
this.currentIndex,
this.onShowMessage,
this.imageDownloader,
this.fileDownloader,
});
}) : assert(currentIndex != null, 'currentIndex cannot be null');
@override
Widget build(BuildContext context) {
@@ -145,19 +145,20 @@ class AttachmentActionsModal extends StatelessWidget {
() {
final channel = StreamChannel.of(context).channel;
if (message.attachments.length > 1 ||
message.text.isNotEmpty) {
message.text?.isNotEmpty == true) {
final remainingAttachments = [...message.attachments]
..removeAt(currentIndex);
channel.updateMessage(message.copyWith(
attachments: remainingAttachments,
));
Navigator.pop(context);
Navigator.pop(context);
Navigator.of(context)
..pop()
..maybePop();
} else {
channel.deleteMessage(message).then((value) {
Navigator.pop(context);
Navigator.pop(context);
});
channel.deleteMessage(message);
Navigator.of(context)
..pop()
..maybePop();
}
},
color: theme.colorTheme.accentRed,
@@ -185,8 +186,10 @@ class AttachmentActionsModal extends StatelessWidget {
StreamSvgIcon icon,
VoidCallback onTap, {
Color color,
Key key,
}) {
return Material(
key: key,
color: StreamChatTheme.of(context).colorTheme.white,
child: InkWell(
onTap: onTap,
@@ -224,7 +227,7 @@ class AttachmentActionsModal extends StatelessWidget {
if (progress == null || progress?.toProgressIndicatorValue == 1.0) {
Future.delayed(
const Duration(milliseconds: 500),
Navigator.of(context).pop,
Navigator.of(context).maybePop,
);
}
return Material(
@@ -248,6 +251,7 @@ class AttachmentActionsModal extends StatelessWidget {
)
: progress.toProgressIndicatorValue == 1.0
? Container(
key: Key('completedIcon'),
height: 160,
width: 160,
child: StreamSvgIcon.check(
@@ -1,182 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'attachment/attachment.dart';
class ChannelFileDisplayScreen extends StatefulWidget {
/// The sorting used for the channels matching the filters.
/// Sorting is based on field and direction, multiple sorting options can be provided.
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
/// Direction can be ascending or descending.
final List<SortOption> sortOptions;
/// Pagination parameters
/// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams paginationParams;
/// The builder used when the file list is empty.
final WidgetBuilder emptyBuilder;
const ChannelFileDisplayScreen({
this.sortOptions,
this.paginationParams,
this.emptyBuilder,
});
@override
_ChannelFileDisplayScreenState createState() =>
_ChannelFileDisplayScreenState();
}
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
@override
void initState() {
super.initState();
final messageSearchBloc = MessageSearchBloc.of(context);
messageSearchBloc.search(
filter: {
'cid': {
r'$in': [StreamChannel.of(context).channel.cid]
}
},
messageFilter: {
'attachments.type': {
r'$in': ['file'],
},
},
sort: widget.sortOptions,
pagination: widget.paginationParams,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
centerTitle: true,
title: Text(
'Files',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.black,
fontSize: 16.0),
),
leading: Center(
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24.0,
),
),
),
),
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
),
body: _buildMediaGrid(),
);
}
Widget _buildMediaGrid() {
final messageSearchBloc = MessageSearchBloc.of(context);
return StreamBuilder<List<GetMessageResponse>>(
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(
child: const CircularProgressIndicator(),
);
}
if (snapshot.data.isEmpty) {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder(context);
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamSvgIcon.files(
size: 136.0,
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
),
SizedBox(height: 16.0),
Text(
'No Files',
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context).colorTheme.black,
),
),
SizedBox(height: 8.0),
Text(
'Files sent in this chat will appear here',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5),
),
),
],
),
);
}
final media = <Attachment, Message>{};
for (var item in snapshot.data) {
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
media[e] = item.message;
});
}
return LazyLoadScrollView(
onEndOfPage: () => messageSearchBloc.search(
filter: {
'cid': {
r'$in': [StreamChannel.of(context).channel.cid]
}
},
messageFilter: {
'attachments.type': {
r'$in': ['file']
},
},
sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0,
),
),
child: ListView.builder(
itemBuilder: (context, position) {
return Padding(
padding: const EdgeInsets.all(1.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FileAttachment(
message: media.values.toList()[position],
attachment: media.keys.toList()[position],
),
),
);
},
itemCount: media.length,
),
);
},
stream: messageSearchBloc.messagesStream,
);
}
}
@@ -50,7 +50,6 @@ class ChannelImage extends StatelessWidget {
this.channel,
this.constraints,
this.onTap,
this.showOnlineStatus = true,
this.borderRadius,
this.selected = false,
this.selectionColor,
@@ -68,8 +67,6 @@ class ChannelImage extends StatelessWidget {
/// The function called when the image is tapped
final VoidCallback onTap;
final bool showOnlineStatus;
final bool selected;
final Color selectionColor;
@@ -81,142 +78,144 @@ class ChannelImage extends StatelessWidget {
final streamChat = StreamChat.of(context);
final channel = this.channel ?? StreamChannel.of(context).channel;
return StreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream,
initialData: channel.extraData,
builder: (context, snapshot) {
String image;
if (snapshot.data?.containsKey('image') == true) {
image = snapshot.data['image'];
} else if (channel.state.members?.length == 2) {
final otherMember = channel.state.members
.firstWhere((member) => member.user.id != streamChat.user.id);
return StreamBuilder<User>(
stream: streamChat.client.state.usersStream
.map((users) => users[otherMember.userId]),
initialData: otherMember.user,
builder: (context, snapshot) {
return UserAvatar(
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
user: snapshot.data ?? otherMember.user,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
onTap: onTap != null ? (_) => onTap() : null,
selected: selected,
selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
selectionThickness: selectionThickness,
);
});
} else {
final images = channel.state.members
.where((member) =>
member.user.id != streamChat.user.id &&
member.user.extraData['image'] != null)
.take(4)
.map((e) => e.user.extraData['image'] as String)
.toList();
return GroupImage(
images: images,
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
onTap: onTap,
selected: selected,
selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
selectionThickness: selectionThickness,
);
}
Widget child = ClipRRect(
stream: channel.extraDataStream,
initialData: channel.extraData,
builder: (context, snapshot) {
String image;
if (snapshot.data?.containsKey('image') == true) {
image = snapshot.data['image'];
} else if (channel.state.members?.length == 2) {
final otherMember = channel.state.members
.firstWhere((member) => member.user.id != streamChat.user.id);
return StreamBuilder<User>(
stream: streamChat.client.state.usersStream
.map((users) => users[otherMember.userId]),
initialData: otherMember.user,
builder: (context, snapshot) {
return UserAvatar(
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
user: snapshot.data ?? otherMember.user,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
onTap: onTap != null ? (_) => onTap() : null,
selected: selected,
selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
selectionThickness: selectionThickness,
);
});
} else {
final images = channel.state.members
.where((member) =>
member.user.id != streamChat.user.id &&
member.user.extraData['image'] != null)
.take(4)
.map((e) => e.user.extraData['image'] as String)
.toList();
return GroupImage(
images: images,
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
child: Container(
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
image != null
? CachedNetworkImage(
imageUrl: image,
errorWidget: (_, __, ___) {
return Center(
child: Text(
snapshot.data?.containsKey('name') ?? false
? snapshot.data['name'][0]
: '',
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.white,
fontWeight: FontWeight.bold,
),
),
);
},
fit: BoxFit.cover,
)
: StreamChatTheme.of(context)
.defaultChannelImage(context, channel),
Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
),
),
],
),
),
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
onTap: onTap,
selected: selected,
selectionColor: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
selectionThickness: selectionThickness,
);
if (selected) {
child = ClipRRect(
borderRadius: (borderRadius ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
.borderRadius) +
BorderRadius.circular(selectionThickness),
child: Container(
constraints: constraints ??
}
Widget child = ClipRRect(
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.borderRadius,
child: Container(
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
image != null
? CachedNetworkImage(
imageUrl: image,
errorWidget: (_, __, ___) {
return Center(
child: Text(
snapshot.data?.containsKey('name') ?? false
? snapshot.data['name'][0]
: '',
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.white,
fontWeight: FontWeight.bold,
),
),
);
},
fit: BoxFit.cover,
)
: StreamChatTheme.of(context)
.defaultChannelImage(context, channel),
Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
),
),
],
),
),
);
if (selected) {
child = ClipRRect(
key: Key('selectedImage'),
borderRadius: (borderRadius ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
.constraints,
color: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: child,
),
.borderRadius) +
BorderRadius.circular(selectionThickness),
child: Container(
constraints: constraints ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
.constraints,
color: selectionColor ??
StreamChatTheme.of(context).colorTheme.accentBlue,
child: Padding(
padding: EdgeInsets.all(selectionThickness),
child: child,
),
);
}
return child;
});
),
);
}
return child;
},
);
}
}
@@ -1,251 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
import 'attachment/attachment.dart';
class ChannelMediaDisplayScreen extends StatefulWidget {
/// The sorting used for the channels matching the filters.
/// Sorting is based on field and direction, multiple sorting options can be provided.
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
/// Direction can be ascending or descending.
final List<SortOption> sortOptions;
/// Pagination parameters
/// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams paginationParams;
/// The builder used when the file list is empty.
final WidgetBuilder emptyBuilder;
final ShowMessageCallback onShowMessage;
const ChannelMediaDisplayScreen({
this.sortOptions,
this.paginationParams,
this.emptyBuilder,
this.onShowMessage,
});
@override
_ChannelMediaDisplayScreenState createState() =>
_ChannelMediaDisplayScreenState();
}
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
Map<String, VideoPlayerController> controllerCache = {};
@override
void initState() {
super.initState();
final messageSearchBloc = MessageSearchBloc.of(context);
messageSearchBloc.search(
filter: {
'cid': {
r'$in': [StreamChannel.of(context).channel.cid],
}
},
messageFilter: {
'attachments.type': {
r'$in': ['image', 'video']
},
},
sort: widget.sortOptions,
pagination: widget.paginationParams,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
appBar: AppBar(
brightness: Theme.of(context).brightness,
elevation: 1,
centerTitle: true,
title: Text(
'Photos & Videos',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.black,
fontSize: 16.0,
),
),
leading: Center(
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24.0,
),
),
),
),
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
),
body: _buildMediaGrid(),
);
}
Widget _buildMediaGrid() {
final messageSearchBloc = MessageSearchBloc.of(context);
return StreamBuilder<List<GetMessageResponse>>(
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(
child: const CircularProgressIndicator(),
);
}
if (snapshot.data.isEmpty) {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder(context);
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamSvgIcon.pictures(
size: 136.0,
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
),
SizedBox(height: 16.0),
Text(
'No Media',
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context).colorTheme.black,
),
),
SizedBox(height: 8.0),
Text(
'Photos or video sent in this chat will \nappear here',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5),
),
),
],
),
);
}
final media = <_AssetPackage>[];
for (var item in snapshot.data) {
item.message.attachments
.where((e) =>
(e.type == 'image' || e.type == 'video') &&
e.ogScrapeUrl == null)
.forEach((e) {
VideoPlayerController controller;
if (e.type == 'video') {
var cachedController = controllerCache[e.assetUrl];
if (cachedController == null) {
controller = VideoPlayerController.network(e.assetUrl);
controller.initialize();
controllerCache[e.assetUrl] = controller;
} else {
controller = cachedController;
}
}
media.add(_AssetPackage(e, item.message, controller));
});
}
return LazyLoadScrollView(
onEndOfPage: () => messageSearchBloc.search(
filter: {
'cid': {
r'$in': [StreamChannel.of(context).channel.cid]
}
},
messageFilter: {
'attachments.type': {
r'$in': ['image', 'video']
},
},
sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0,
),
),
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (context, position) {
var channel = StreamChannel.of(context).channel;
return Padding(
padding: const EdgeInsets.all(1.0),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments:
media.map((e) => e.attachment).toList(),
startIndex: position,
message: media[position].message,
sentAt: media[position].message.createdAt,
userName: media[position].message.user.name,
onShowMessage: widget.onShowMessage,
),
),
),
);
},
child: media[position].attachment.type == 'image'
? IgnorePointer(
child: ImageAttachment(
attachment: media[position].attachment,
message: media[position].message,
showTitle: false,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
),
)
: VideoPlayer(media[position].videoPlayer),
),
);
},
itemCount: media.length,
),
);
},
stream: messageSearchBloc.messagesStream,
);
}
@override
void dispose() {
super.dispose();
for (var c in controllerCache.values) {
c.dispose();
}
}
}
class _AssetPackage {
Attachment attachment;
Message message;
VideoPlayerController videoPlayer;
_AssetPackage(this.attachment, this.message, this.videoPlayer);
}
@@ -6,7 +6,6 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import '../stream_chat_flutter.dart';
import 'channel_name.dart';
import 'channel_unread_indicator.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png)
@@ -98,19 +97,20 @@ class ChannelPreview extends StatelessWidget {
),
),
StreamBuilder<List<Member>>(
stream: channel.state.membersStream,
initialData: channel.state.members,
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.data.isEmpty ||
!snapshot.data.any((Member e) =>
e.user.id == channel.client.state.user.id)) {
return SizedBox();
}
return ChannelUnreadIndicator(
channel: channel,
);
}),
stream: channel.state.membersStream,
initialData: channel.state.members,
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.data.isEmpty ||
!snapshot.data.any((Member e) =>
e.user.id == channel.client.state.user.id)) {
return SizedBox();
}
return UnreadIndicator(
cid: channel.cid,
);
},
),
],
),
subtitle: Row(
@@ -1,49 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
class ChannelUnreadIndicator extends StatelessWidget {
const ChannelUnreadIndicator({
Key key,
@required this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: channel.state.unreadCountStream,
initialData: channel.state.unreadCount,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == 0) {
return SizedBox();
}
return Material(
borderRadius: BorderRadius.circular(8),
color: StreamChatTheme.of(context)
.channelPreviewTheme
.unreadCounterColor,
child: Padding(
padding: const EdgeInsets.only(
left: 5.0,
right: 5.0,
top: 2,
bottom: 1,
),
child: Center(
child: Text(
'${snapshot.data > 99 ? '99+' : snapshot.data}',
style: TextStyle(
fontSize: 11,
color: Colors.white,
),
),
),
),
);
},
);
}
}
@@ -11,14 +11,15 @@ class InfoTile extends StatelessWidget {
final TextStyle textStyle;
final Color backgroundColor;
InfoTile(
{this.message,
this.child,
this.showMessage,
this.tileAnchor,
this.childAnchor,
this.textStyle,
this.backgroundColor});
InfoTile({
this.message,
this.child,
this.showMessage,
this.tileAnchor,
this.childAnchor,
this.textStyle,
this.backgroundColor,
});
@override
Widget build(BuildContext context) {
@@ -3,7 +3,6 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
@@ -18,11 +17,12 @@ import 'stream_chat_theme.dart';
class MessageActionsModal extends StatefulWidget {
final Widget Function(BuildContext, Message) editMessageInputBuilder;
final void Function(Message) onThreadReplyTap;
final void Function(Message) onReplyTap;
final OnMessageTap onThreadReplyTap;
final OnMessageTap onReplyTap;
final Message message;
final MessageTheme messageTheme;
final bool showReactions;
final OnMessageTap onCopyTap;
final bool showDeleteMessage;
final bool showCopyMessage;
final bool showEditMessage;
@@ -60,6 +60,7 @@ class MessageActionsModal extends StatefulWidget {
this.reverse = false,
this.customActions = const [],
this.attachmentBorderRadiusGeometry,
this.onCopyTap,
}) : super(key: key);
@override
@@ -211,10 +212,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
CrossAxisAlignment.stretch,
children: [
if (widget.showReplyMessage &&
(widget.message.status ==
widget.message.status ==
MessageSendingStatus.sent ||
widget.message.status == null) &&
widget.message.parentId == null)
widget.message.status == null)
_buildReplyButton(context),
if (widget.showThreadReplyMessage &&
(widget.message.status ==
@@ -452,7 +452,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Widget _buildCopyButton(BuildContext context) {
return InkWell(
onTap: () async {
await Clipboard.setData(ClipboardData(text: widget.message.text));
widget.onCopyTap?.call(widget.message);
Navigator.pop(context);
},
child: Padding(
@@ -693,7 +693,7 @@ class MessageInputState extends State<MessageInput> {
if (!mounted) {
return;
}
StreamChannel.of(context).channel.keyStroke().catchError((e) {});
StreamChannel.of(context).channel.keyStroke()?.catchError((e) {});
setState(() {
_messageIsPresent = s.trim().isNotEmpty;
@@ -2181,10 +2181,12 @@ class MessageInputState extends State<MessageInput> {
void _parseExistingMessage(Message message) {
textEditingController.text = message.text;
_messageIsPresent = true;
for (final attachment in message?.attachments) {
_attachments[attachment.id] = attachment.copyWith(
uploadState: attachment.uploadState ?? UploadState.success(),
);
if (message.attachments != null) {
for (final attachment in message.attachments) {
_attachments[attachment.id] = attachment.copyWith(
uploadState: attachment.uploadState ?? UploadState.success(),
);
}
}
}
@@ -13,8 +13,6 @@ import 'message_widget.dart';
import 'stream_chat_theme.dart';
class MessageReactionsModal extends StatelessWidget {
final Widget Function(BuildContext, Message) editMessageInputBuilder;
final void Function(Message) onThreadTap;
final Message message;
final MessageTheme messageTheme;
final bool reverse;
@@ -30,8 +28,6 @@ class MessageReactionsModal extends StatelessWidget {
@required this.message,
@required this.messageTheme,
this.showReactions = true,
this.onThreadTap,
this.editMessageInputBuilder,
this.messageShape,
this.attachmentShape,
this.reverse = false,
@@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
@@ -841,6 +842,8 @@ class _MessageWidgetState extends State<MessageWidget>
return StreamChannel(
channel: channel,
child: MessageActionsModal(
onCopyTap: (message) =>
Clipboard.setData(ClipboardData(text: message.text)),
attachmentBorderRadiusGeometry:
widget.attachmentBorderRadiusGeometry,
showUserAvatar:
@@ -903,8 +906,6 @@ class _MessageWidgetState extends State<MessageWidget>
widget.attachmentShape ?? _getDefaultAttachmentShape(context),
reverse: widget.reverse,
message: widget.message,
editMessageInputBuilder: widget.editMessageInputBuilder,
onThreadTap: widget.onThreadTap,
showReactions: widget.showReactions,
),
);
@@ -2,6 +2,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -36,67 +37,70 @@ class ReactionBubble extends StatelessWidget {
return Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: Stack(
alignment: Alignment.center,
children: [
Transform.translate(
offset: Offset(reverse ? offset : -offset, 0),
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: maskColor,
borderRadius: BorderRadius.all(Radius.circular(16)),
),
child: Center(
child: Stack(
alignment: Alignment.center,
fit: StackFit.loose,
children: [
Transform.translate(
offset: Offset(reverse ? offset : -offset, 0),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: totalReactions > 1 ? 4 : 0,
),
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
border: Border.all(
color: borderColor,
),
color: backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(14)),
color: maskColor,
borderRadius: BorderRadius.all(Radius.circular(16)),
),
child: LayoutBuilder(
builder: (context, constraints) {
return Flex(
direction: Axis.horizontal,
mainAxisSize: MainAxisSize.min,
children: [
if (constraints.maxWidth < double.infinity)
...reactions
.take((constraints.maxWidth) ~/ 24)
.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
if (constraints.maxWidth == double.infinity)
...reactions.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
],
);
},
child: Container(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: totalReactions > 1 ? 4 : 0,
),
decoration: BoxDecoration(
border: Border.all(
color: borderColor,
),
color: backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(14)),
),
child: LayoutBuilder(
builder: (context, constraints) {
return Flex(
direction: Axis.horizontal,
mainAxisSize: MainAxisSize.min,
children: [
if (constraints.maxWidth < double.infinity)
...reactions
.take((constraints.maxWidth) ~/ 24)
.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
if (constraints.maxWidth == double.infinity)
...reactions.map((reaction) {
return _buildReaction(
reactionIcons,
reaction,
context,
);
}).toList(),
],
);
},
),
),
),
),
),
Positioned(
bottom: 2,
left: reverse ? null : 13,
right: !reverse ? null : 13,
child: _buildReactionsTail(context),
),
],
Positioned(
bottom: 2,
left: reverse ? null : 13,
right: !reverse ? null : 13,
child: _buildReactionsTail(context),
),
],
),
),
);
}
@@ -40,7 +40,7 @@ class UnreadIndicator extends StatelessWidget {
),
child: Center(
child: Text(
'${snapshot.data}',
'${snapshot.data > 99 ? '99+' : snapshot.data}',
style: TextStyle(
fontSize: 11,
color: Colors.white,
@@ -1,3 +1,6 @@
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
export 'src/attachment/attachment.dart';
export 'src/back_button.dart';
export 'src/channel_header.dart';
export 'src/channel_image.dart';
@@ -5,39 +8,35 @@ export 'src/channel_list_header.dart';
export 'src/channel_list_view.dart';
export 'src/channel_name.dart';
export 'src/channel_preview.dart';
export 'src/connection_status_builder.dart';
export 'src/date_divider.dart';
export 'src/deleted_message.dart';
export 'src/message_action.dart';
export 'src/attachment/attachment.dart';
export 'src/full_screen_media.dart';
export 'src/image_header.dart';
export 'src/image_footer.dart';
export 'src/image_header.dart';
export 'src/info_tile.dart';
export 'src/mention_tile.dart';
export 'src/message_action.dart';
export 'src/message_input.dart';
export 'src/message_list_view.dart';
export 'src/message_search_item.dart';
export 'src/message_search_list_view.dart';
export 'src/message_text.dart';
export 'src/message_widget.dart';
export 'src/option_list_tile.dart';
export 'src/reaction_picker.dart';
export 'src/sending_indicator.dart';
export 'src/stream_chat.dart';
export 'src/stream_chat_theme.dart';
export 'src/stream_neumorphic_button.dart';
export 'src/stream_svg_icon.dart';
export 'src/system_message.dart';
export 'src/thread_header.dart';
export 'src/typing_indicator.dart';
export 'src/unread_indicator.dart';
export 'src/user_avatar.dart';
export 'src/user_item.dart';
export 'src/user_item.dart';
export 'src/user_list_view.dart';
export 'src/user_list_view.dart';
export 'src/utils.dart';
export 'src/message_search_item.dart';
export 'src/message_search_list_view.dart';
export 'src/unread_indicator.dart';
export 'src/option_list_tile.dart';
export 'src/channel_file_display_screen.dart';
export 'src/channel_media_display_screen.dart';
export 'src/info_tile.dart';
export 'src/stream_chat.dart';
export 'src/connection_status_builder.dart';
export 'src/mention_tile.dart';
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+4 -1
View File
@@ -55,9 +55,12 @@ flutter:
- svgs/
- lib/svgs/
- animations/
uses-material-design: true
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^4.1.3
pedantic: ^1.9.2
pedantic: ^1.9.2
golden_toolkit: ^0.6.0
@@ -0,0 +1,9 @@
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
await loadAppFonts();
return testMain();
}
@@ -0,0 +1,510 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/attachment_actions_modal.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
class MockAttachmentDownloader extends Mock {
ProgressCallback progressCallback;
Completer<String> completer = Completer();
Future<String> call(
Attachment attachment, {
ProgressCallback progressCallback,
}) {
this.progressCallback = progressCallback;
return completer.future;
}
}
void main() {
testWidgets(
'it should show all the actions',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: AttachmentActionsModal(
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'text.jpg',
),
],
),
currentIndex: 0,
),
),
),
);
expect(find.text('Reply'), findsOneWidget);
expect(find.text('Show in Chat'), findsOneWidget);
expect(find.text('Save Image'), findsOneWidget);
expect(find.text('Delete'), findsOneWidget);
},
);
testWidgets(
'it should hide delete if it\'s not my message',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id2'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: AttachmentActionsModal(
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'text.jpg',
),
],
),
currentIndex: 0,
),
),
),
);
expect(find.text('Reply'), findsOneWidget);
expect(find.text('Show in Chat'), findsOneWidget);
expect(find.text('Save Image'), findsOneWidget);
expect(find.text('Delete'), findsNothing);
},
);
testWidgets(
'it should show save video for videos',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: Container(
child: AttachmentActionsModal(
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'video',
title: 'video.mp4',
),
],
),
currentIndex: 0,
),
),
),
),
);
expect(find.text('Save Video'), findsOneWidget);
},
);
testWidgets(
'tapping on reply should pop',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
final mockObserver = MockNavigatorObserver();
final message = Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'image.jpg',
),
],
);
await tester.pumpWidget(
MaterialApp(
theme: themeData,
navigatorObservers: [mockObserver],
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: Container(
child: AttachmentActionsModal(
message: message,
currentIndex: 0,
),
),
),
),
);
await tester.tap(find.text('Reply'));
verify(mockObserver.didPop(any, any));
},
);
testWidgets(
'tapping on show in chat should call onShowMessage',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
final onShowMessage = MockVoidCallback();
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: Container(
child: AttachmentActionsModal(
onShowMessage: onShowMessage,
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'image.jpg',
),
]),
currentIndex: 0,
),
),
),
),
);
await tester.tap(find.text('Show in Chat'));
verify(onShowMessage.call()).called(1);
},
);
testWidgets(
'tapping on delete in chat should remove the attachment',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final mockChannel = MockChannel();
when(mockChannel.updateMessage(any)).thenAnswer((_) {
return;
});
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final message = Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'image.jpg',
),
Attachment(
type: 'image',
title: 'image.jpg',
),
],
);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
child: child,
);
},
home: StreamChannel(
showLoading: false,
channel: mockChannel,
child: AttachmentActionsModal(
message: message,
currentIndex: 0,
),
),
),
);
await tester.tap(find.text('Delete'));
verify(mockChannel.updateMessage(message.copyWith(
attachments: [
message.attachments[1],
],
))).called(1);
},
);
testWidgets(
'tapping on delete in chat should remove the attachment if there is text',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final mockChannel = MockChannel();
when(mockChannel.updateMessage(any)).thenAnswer((_) {
return;
});
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final message = Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'image.jpg',
),
],
);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
child: child,
);
},
home: StreamChannel(
showLoading: false,
channel: mockChannel,
child: AttachmentActionsModal(
message: message,
currentIndex: 0,
),
),
),
);
await tester.tap(find.text('Delete'));
verify(mockChannel.updateMessage(message.copyWith(
attachments: [],
))).called(1);
},
);
testWidgets(
'tapping on delete in chat should remove the message if that\'s the only attachment and there is no text',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final mockChannel = MockChannel();
when(mockChannel.deleteMessage(any)).thenAnswer((_) {
return;
});
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final message = Message(
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'image.jpg',
),
],
);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
child: child,
);
},
home: StreamChannel(
showLoading: false,
channel: mockChannel,
child: AttachmentActionsModal(
message: message,
currentIndex: 0,
),
),
),
);
await tester.tap(find.text('Delete'));
verify(mockChannel.deleteMessage(message)).called(1);
},
);
testWidgets(
'tapping on save in chat should call image downloader',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final imageDownloader = MockAttachmentDownloader();
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
child: child,
);
},
home: Container(
child: AttachmentActionsModal(
imageDownloader: imageDownloader,
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'image',
title: 'image.jpg',
),
]),
currentIndex: 0,
),
),
),
);
await tester.tap(find.text('Save Image'));
imageDownloader.progressCallback(0, 100);
await tester.pump();
expect(find.text('0%'), findsOneWidget);
imageDownloader.progressCallback(50, 100);
await tester.pump();
expect(find.text('50%'), findsOneWidget);
imageDownloader.progressCallback(100, 100);
imageDownloader.completer.complete('path');
await tester.pump();
expect(find.byKey(Key('completedIcon')), findsOneWidget);
await tester.pumpAndSettle(Duration(milliseconds: 500));
},
);
testWidgets(
'tapping on save in chat should call file downloader',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final fileDownloader = MockAttachmentDownloader();
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
child: child,
);
},
home: Container(
child: AttachmentActionsModal(
fileDownloader: fileDownloader,
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
attachments: [
Attachment(
type: 'video',
title: 'video.mp4',
),
]),
currentIndex: 0,
),
),
),
);
await tester.tap(find.text('Save Video'));
fileDownloader.progressCallback(0, 100);
await tester.pump();
expect(find.text('0%'), findsOneWidget);
fileDownloader.progressCallback(50, 100);
await tester.pump();
expect(find.text('50%'), findsOneWidget);
fileDownloader.progressCallback(100, 100);
fileDownloader.completer.complete('path');
await tester.pump();
expect(find.byKey(Key('completedIcon')), findsOneWidget);
await tester.pumpAndSettle(Duration(milliseconds: 500));
},
);
}
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show file details',
(WidgetTester tester) async {
final channel = MockChannel();
final channelState = MockChannelState();
when(channel.state).thenReturn(channelState);
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
home: StreamChatTheme(
data: streamTheme,
child: StreamChannel(
channel: channel,
child: Container(
child: FileAttachment(
size: Size(
300.0,
300.0,
),
message: Message(),
attachment: Attachment(
type: 'file',
title: 'example.pdf',
extraData: {
'mime_type': 'pdf',
},
),
),
),
),
),
),
);
expect(find.text('example.pdf'), findsOneWidget);
},
);
}
@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/back_button.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'BackButton control test',
(WidgetTester tester) async {
final theme = ThemeData();
await tester.pumpWidget(
MaterialApp(
home: const Material(child: Text('Home')),
routes: <String, WidgetBuilder>{
'/next': (BuildContext context) {
return Material(
child: Center(
child: StreamChatTheme(
data: StreamChatThemeData.getDefaultTheme(theme),
child: StreamBackButton(),
),
),
);
},
},
),
);
// ignore: unawaited_futures
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pumpAndSettle();
await tester.tap(find.byType(StreamBackButton));
await tester.pumpAndSettle();
expect(find.text('Home'), findsOneWidget);
},
);
testWidgets(
'it should not throw errors if cannot pop',
(WidgetTester tester) async {
final theme = ThemeData();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StreamChatTheme(
data: StreamChatThemeData.getDefaultTheme(theme),
child: StreamBackButton(),
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byType(StreamBackButton));
await tester.pumpAndSettle();
expect(find.byType(StreamBackButton), findsOneWidget);
},
);
testWidgets(
'BackButton onPressed overrides default pop behavior',
(WidgetTester tester) async {
final theme = ThemeData();
var customCallbackWasCalled = false;
await tester.pumpWidget(
MaterialApp(
home: const Material(child: Text('Home')),
routes: <String, WidgetBuilder>{
'/next': (BuildContext context) {
return Material(
child: Center(
child: StreamChatTheme(
data: StreamChatThemeData.getDefaultTheme(theme),
child: StreamBackButton(
onPressed: () => customCallbackWasCalled = true,
),
),
),
);
},
},
),
);
// ignore: unawaited_futures
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pumpAndSettle();
expect(find.text('Home'), findsNothing); // Start off on the second page.
expect(
customCallbackWasCalled,
false,
); // customCallbackWasCalled should still be false.
await tester.tap(find.byType(StreamBackButton));
await tester.pumpAndSettle();
// We're still on the second page.
expect(find.text('Home'), findsNothing);
// But the custom callback is called.
expect(customCallbackWasCalled, true);
},
);
testWidgets(
'it should show unread count',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StreamChat(
client: client,
child: StreamBackButton(
showUnreads: true,
),
),
),
),
),
);
expect(find.byType(UnreadIndicator), findsOneWidget);
},
);
}
@@ -0,0 +1,388 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show basic channel information',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelHeader(),
),
),
),
));
expect(find.text('test'), findsOneWidget);
expect(find.byType(ChannelImage), findsOneWidget);
expect(find.byType(StreamBackButton), findsOneWidget);
expect(find.byType(ChannelInfo), findsOneWidget);
},
);
testWidgets(
'it should show the InfoTile message if disconnected',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.disconnected));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelHeader(
showConnectionStateTile: true,
),
),
),
),
));
expect(tester.widget<InfoTile>(find.byType(InfoTile)).showMessage, true);
expect(tester.widget<InfoTile>(find.byType(InfoTile)).message,
'Disconnected');
},
);
testWidgets(
'it should show the InfoTile message if connecting',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
when(channel.initialized).thenAnswer((_) => Future.value(true));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
showLoading: false,
child: Scaffold(
body: ChannelHeader(
showConnectionStateTile: true,
),
),
),
),
));
await tester.pump();
expect(tester.widget<InfoTile>(find.byType(InfoTile)).showMessage, true);
expect(tester.widget<InfoTile>(find.byType(InfoTile)).message,
'Reconnecting...');
},
);
testWidgets(
'it should apply passed properties',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelHeader(
leading: Text('leading'),
subtitle: Text('subtitle'),
actions: [
Text('action'),
],
title: Text('title'),
),
),
),
),
));
expect(find.text('test'), findsNothing);
expect(find.byType(StreamBackButton), findsNothing);
expect(find.byType(ChannelImage), findsNothing);
expect(find.byType(ChannelInfo), findsNothing);
expect(find.text('leading'), findsOneWidget);
expect(find.text('title'), findsOneWidget);
expect(find.text('subtitle'), findsOneWidget);
expect(find.text('action'), findsOneWidget);
},
);
testWidgets(
'showBackButton: false should hide the StreamBackButton and '
'showTypingIndicator: false should hide the typing indicator and '
'showConnectionStateTile: false should be passed to the infotile',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.disconnected));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelHeader(
showTypingIndicator: false,
showBackButton: false,
showConnectionStateTile: false,
),
),
),
),
));
expect(find.byType(StreamBackButton), findsNothing);
expect(
tester
.widget<ChannelInfo>(find.byType(ChannelInfo))
.showTypingIndicator,
false);
expect(tester.widget<InfoTile>(find.byType(InfoTile)).showMessage, false);
},
);
testWidgets(
'should apply passed callbacks',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
var backPressed = false;
var imageTapped = false;
var titleTapped = false;
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelHeader(
onBackPressed: () => backPressed = true,
onImageTap: () => imageTapped = true,
onTitleTap: () => titleTapped = true,
),
),
),
),
));
await tester.tap(find.byType(StreamBackButton));
await tester.tap(find.byType(ChannelImage));
await tester.tap(find.byType(ChannelName));
expect(backPressed, true);
expect(imageTapped, true);
expect(titleTapped, true);
},
);
}
@@ -0,0 +1,263 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/group_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show the image in channel.extraData',
(tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
'image': 'imagetest',
}));
when(channel.extraData).thenReturn({
'name': 'test',
'image': 'imagetest',
});
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelImage(),
),
),
),
));
final image =
tester.widget<CachedNetworkImage>(find.byType(CachedNetworkImage));
expect(image.imageUrl, 'imagetest');
},
);
testWidgets(
'it should show the the other member image',
(tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
Member(
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
'image': 'testimage',
},
),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
'image': 'testimage',
},
),
),
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]);
when(clientState.usersStream).thenAnswer((i) => Stream.value({
'user-id2': User(
id: 'user-id2',
extraData: {
'image': 'testimage',
},
),
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelImage(),
),
),
),
));
final image =
tester.widget<CachedNetworkImage>(find.byType(CachedNetworkImage));
expect(image.imageUrl, 'testimage');
},
);
testWidgets(
'it should use a groupimage if more than 2 members',
(tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(
id: 'user-id',
extraData: {
'image': 'testimage1',
},
),
),
Member(
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
'image': 'testimage2',
},
),
),
Member(
userId: 'user-id3',
user: User(
id: 'user-id3',
extraData: {
'image': 'testimage3',
},
),
),
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(
id: 'user-id',
extraData: {
'image': 'testimage1',
},
),
),
Member(
userId: 'user-id2',
user: User(
id: 'user-id2',
extraData: {
'image': 'testimage2',
},
),
),
Member(
userId: 'user-id3',
user: User(
id: 'user-id3',
extraData: {
'image': 'testimage3',
},
),
),
]);
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelImage(),
),
),
),
));
final image = tester.widget<GroupImage>(find.byType(GroupImage));
expect(image.images, [
'testimage2',
'testimage3',
]);
},
);
testWidgets(
'using select: true should show a selection border',
(tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
'image': 'imagetest',
}));
when(channel.extraData).thenReturn({
'name': 'test',
'image': 'imagetest',
});
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelImage(
selected: true,
),
),
),
),
));
expect(find.byKey(Key('selectedImage')), findsOneWidget);
},
);
}
@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'control test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connected));
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListHeader(),
),
),
),
);
await tester.pumpAndSettle();
final userAvatar = tester.widget<UserAvatar>(find.byType(UserAvatar));
expect(userAvatar.user, clientState.user);
expect(find.byType(StreamNeumorphicButton), findsOneWidget);
expect(find.text('Stream Chat'), findsOneWidget);
},
);
testWidgets(
'it should show the InfoTile message if disconnected',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.disconnected));
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListHeader(
showConnectionStateTile: true,
),
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Disconnected'), findsOneWidget);
},
);
testWidgets(
'it should show the InfoTile message if connecting',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListHeader(
showConnectionStateTile: true,
),
),
),
),
);
await tester.pump();
expect(find.text('Reconnecting...'), findsOneWidget);
},
);
testWidgets(
'it should apply passed properties',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListHeader(
titleBuilder: (context, status, client) {
return Text('TITLE');
},
subtitle: Text('SUBTITLE'),
leading: Text('LEADING'),
actions: [
Text('ACTION'),
],
client: client,
),
),
),
),
);
await tester.pump();
expect(find.text('TITLE'), findsOneWidget);
expect(find.text('SUBTITLE'), findsOneWidget);
expect(find.text('LEADING'), findsOneWidget);
expect(find.text('ACTION'), findsOneWidget);
},
);
testWidgets(
'it should apply prenavigationcallback',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
var tapped = false;
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListHeader(
preNavigationCallback: () {
tapped = true;
},
),
),
),
),
);
await tester.pump();
await tester.tap(find.byType(UserAvatar));
expect(tapped, true);
},
);
testWidgets(
'it should apply passed callbacks',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(client.wsConnectionStatusStream)
.thenAnswer((_) => Stream.value(ConnectionStatus.connecting));
var tapped = 0;
await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListHeader(
onUserAvatarTap: (u) {
tapped++;
},
onNewChatButtonTap: () {
tapped++;
},
),
),
),
),
);
await tester.pump();
await tester.tap(find.byType(UserAvatar));
await tester.tap(find.byType(StreamNeumorphicButton));
expect(tapped, 2);
},
);
}
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show channel name',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(channelState.messages).thenReturn([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]);
when(channelState.messagesStream).thenAnswer((i) => Stream.value([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ChannelName(),
),
),
),
));
expect(find.text('test'), findsOneWidget);
},
);
}
@@ -15,6 +15,7 @@ void main() {
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(channel.cid).thenReturn('cid');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
@@ -28,6 +29,9 @@ void main() {
when(channel.extraData).thenReturn({
'name': 'test name',
});
when(clientState.channels).thenReturn({
channel.cid: channel,
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show basic channel information',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: DateDivider(
dateTime: DateTime.now(),
),
),
),
));
expect(find.text('Today'), findsOneWidget);
},
);
}
@@ -0,0 +1,201 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'control test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: DeletedMessage(
messageTheme: MessageTheme(
createdAt: TextStyle(
color: Colors.black,
),
messageText: TextStyle(),
),
),
),
),
));
expect(find.text('Message deleted'), findsOneWidget);
},
);
testGoldens(
'control golden light',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
final materialTheme = ThemeData.light();
final theme = StreamChatThemeData.fromTheme(materialTheme);
await tester.pumpWidgetBuilder(
materialAppWrapper(
theme: materialTheme,
)(
StreamChat(
streamChatThemeData: theme,
client: client,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Center(
child: DeletedMessage(
messageTheme: theme.ownMessageTheme,
),
),
),
),
),
surfaceSize: Size.square(200),
);
await screenMatchesGolden(tester, 'deleted_message_light');
},
);
testGoldens(
'control golden dark',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
final materialTheme = ThemeData.dark();
final theme = StreamChatThemeData.fromTheme(materialTheme);
await tester.pumpWidgetBuilder(
materialAppWrapper(
theme: materialTheme,
)(
StreamChat(
streamChatThemeData: theme,
client: client,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Center(
child: DeletedMessage(
messageTheme: theme.ownMessageTheme,
),
),
),
),
),
surfaceSize: Size.square(200),
);
await screenMatchesGolden(tester, 'deleted_message_dark');
},
);
testGoldens(
'golden customization test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
final materialTheme = ThemeData.light();
final theme = StreamChatThemeData.fromTheme(materialTheme);
await tester.pumpWidgetBuilder(
materialAppWrapper(
theme: materialTheme,
)(
StreamChat(
streamChatThemeData: theme,
client: client,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Center(
child: DeletedMessage(
messageTheme: theme.ownMessageTheme,
reverse: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
),
surfaceSize: Size.square(200),
);
await screenMatchesGolden(tester, 'deleted_message_custom');
},
);
}
@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show channel typing',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(channelState.messages).thenReturn([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]);
when(channelState.messagesStream).thenAnswer((i) => Stream.value([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]));
when(channelState.typingEvents).thenAnswer((i) => [
User(id: 'other-user', extraData: {'name': 'demo'})
]);
when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([
User(id: 'other-user', extraData: {'name': 'demo'}),
User(id: 'other-user', extraData: {'name': 'demo'}),
]));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments: [
Attachment(
type: 'image',
title: 'demo image',
imageUrl: '',
),
],
message: Message(
createdAt: DateTime.now(),
),
sentAt: DateTime.now(),
),
),
),
));
expect(find.byType(PhotoView), findsOneWidget);
expect(find.byType(StreamSvgIcon), findsNWidgets(4));
},
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show channel typing',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: WillPopScope(
onWillPop: () async => false,
child: Scaffold(
body: ImageFooter(),
),
),
),
),
));
expect(find.byType(StreamSvgIcon), findsNWidgets(2));
},
);
}
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'control test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: Portal(
child: Container(
child: InfoTile(
showMessage: true,
message: 'message',
child: Text('test'),
),
),
),
),
),
));
expect(find.text('message'), findsOneWidget);
},
);
testWidgets(
'it should hide when passing showMessage: false',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: Portal(
child: Container(
child: InfoTile(
showMessage: false,
message: 'message',
child: Text('test'),
),
),
),
),
),
));
expect(find.text('message'), findsNothing);
},
);
}
@@ -38,16 +38,17 @@ void main() {
),
),
);
await tester.pump();
await tester.pumpAndSettle();
await tester.pump(Duration(milliseconds: 1000));
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
expect(find.text('Thread Reply'), findsOneWidget);
expect(find.text('Reply'), findsOneWidget);
expect(find.text('Edit Message'), findsOneWidget);
expect(find.text('Delete Message'), findsOneWidget);
expect(find.text('Copy Message'), findsOneWidget);
},
);
testWidgets(
'it should show some actions',
(WidgetTester tester) async {
@@ -84,9 +85,8 @@ void main() {
),
),
);
await tester.pump();
await tester.pumpAndSettle();
await tester.pump(Duration(milliseconds: 1000));
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
expect(find.text('Reply'), findsNothing);
expect(find.text('Thread reply'), findsNothing);
@@ -95,4 +95,697 @@ void main() {
expect(find.text('Copy message'), findsNothing);
},
);
testWidgets(
'it should show custom actions',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
var tapped = false;
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: Container(
child: MessageActionsModal(
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
customActions: [
MessageAction(
leading: Icon(Icons.check),
title: Text('title'),
onTap: (m) {
tapped = true;
},
),
],
),
),
),
),
);
await tester.pumpAndSettle();
expect(find.byIcon(Icons.check), findsOneWidget);
expect(find.text('title'), findsOneWidget);
await tester.tap(find.text('title'));
expect(tapped, true);
},
);
testWidgets(
'tapping on reply should call the callback',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
var tapped = false;
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: Container(
child: MessageActionsModal(
onReplyTap: (m) {
return tapped = true;
},
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Reply'));
expect(tapped, true);
},
);
testWidgets(
'tapping on thread reply should call the callback',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
var tapped = false;
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: Container(
child: MessageActionsModal(
onThreadReplyTap: (m) {
return tapped = true;
},
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Thread Reply'));
expect(tapped, true);
},
);
testWidgets(
'tapping on edit should show the edit bottom sheet',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Edit Message'));
await tester.pumpAndSettle();
expect(find.byType(MessageInput), findsOneWidget);
},
);
testWidgets(
'tapping on edit should show use the custom builder',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
editMessageInputBuilder: (context, m) {
return Text('test');
},
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Edit Message'));
await tester.pumpAndSettle();
expect(find.text('test'), findsOneWidget);
},
);
testWidgets(
'tapping on copy should use the callback',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
var tapped = false;
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
onCopyTap: (m) => tapped = true,
message: Message(
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Copy Message'));
expect(tapped, true);
},
);
testWidgets(
'tapping on resend should call send message',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
status: MessageSendingStatus.failed,
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Resend'));
verify(channel.sendMessage(any)).called(1);
},
);
testWidgets(
'tapping on resend should call update message if editing the message',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
status: MessageSendingStatus.failed_update,
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Resend Edited Message'));
verify(channel.updateMessage(any)).called(1);
},
);
testWidgets(
'tapping on flag message should show the dialog',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
id: 'testid',
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Flag Message'));
await tester.pumpAndSettle();
expect(find.text('Flag Message'), findsNWidgets(2));
await tester.tap(find.text('FLAG'));
await tester.pumpAndSettle();
verify(client.flagMessage('testid')).called(1);
},
);
testWidgets(
'if flagging a message throws an error the error dialog should appear',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(client.flagMessage(any)).thenThrow(ApiError(
'{}',
500,
));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
id: 'testid',
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Flag Message'));
await tester.pumpAndSettle();
expect(find.text('Flag Message'), findsNWidgets(2));
await tester.tap(find.text('FLAG'));
await tester.pumpAndSettle();
expect(find.text('Something went wrong'), findsOneWidget);
},
);
testWidgets(
'if flagging an already flagged message no error should appear',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(client.flagMessage(any)).thenThrow(ApiError(
'{"code":4}',
400,
));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
id: 'testid',
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Flag Message'));
await tester.pumpAndSettle();
expect(find.text('Flag Message'), findsNWidgets(2));
await tester.tap(find.text('FLAG'));
await tester.pumpAndSettle();
expect(find.text('Message flagged'), findsOneWidget);
},
);
testWidgets(
'tapping on delete message should call client.delete',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
id: 'testid',
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Delete Message'));
await tester.pumpAndSettle();
expect(find.text('Delete message'), findsOneWidget);
await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle();
verify(channel.deleteMessage(any)).called(1);
},
);
testWidgets(
'tapping on delete message should call client.delete',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.deleteMessage(any)).thenThrow(ApiError(
'{}',
500,
));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
builder: (context, child) {
return StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: child,
);
},
theme: themeData,
home: StreamChannel(
showLoading: false,
channel: channel,
child: Container(
child: MessageActionsModal(
message: Message(
id: 'testid',
text: 'test',
user: User(
id: 'user-id',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Delete Message'));
await tester.pumpAndSettle();
expect(find.text('Delete message'), findsOneWidget);
await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle();
expect(find.text('Something went wrong'), findsOneWidget);
},
);
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'checks message input features',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(channelState.messages).thenReturn([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]);
when(channelState.messagesStream).thenAnswer((i) => Stream.value([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]));
when(channelState.typingEvents).thenAnswer((i) => [
User(id: 'other-user', extraData: {'name': 'demo'})
]);
when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([
User(id: 'other-user', extraData: {'name': 'demo'}),
User(id: 'other-user', extraData: {'name': 'demo'}),
]));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: MessageInput(),
),
),
),
));
expect(find.byType(TextField), findsOneWidget);
expect(find.byType(StreamSvgIcon), findsNWidgets(8));
expect(find.byKey(Key('messageInputText')), findsOneWidget);
},
);
}
@@ -1,102 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show one thumbs from the picker',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: MessageReactionsModal(
message: Message(
id: 'test',
text: 'test message',
user: User(
id: 'test-user',
),
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
);
await tester.pump(Duration(milliseconds: 1000));
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')),
findsOneWidget);
},
);
testWidgets(
'it should show two reactions',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
final testUserId = 'test user';
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
child: MessageReactionsModal(
message: Message(
text: 'test message',
user: User(
id: 'test-user',
),
latestReactions: [
Reaction(
type: 'like',
user: User(id: testUserId),
),
Reaction(
type: 'love',
user: User(id: testUserId),
),
],
),
messageTheme: streamTheme.ownMessageTheme,
),
),
),
);
await tester.pump();
await tester.pump(Duration(milliseconds: 1000));
expect(find.byKey(Key('MessageWidget')), findsOneWidget);
expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')),
findsNWidgets(2));
expect(find.byKey(Key('StreamSvgIcon-Icon_love_reaction.svg')),
findsNWidgets(2));
expect(find.text(testUserId.split(' ')[0]), findsNWidgets(2));
},
);
}
@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'control test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
final message = Message(
id: 'test',
text: 'test message',
user: User(
id: 'test-user',
),
);
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: MessageReactionsModal(
message: message,
messageTheme: streamTheme.ownMessageTheme,
),
),
),
);
await tester.pump(Duration(milliseconds: 1000));
expect(find.byType(MessageWidget), findsOneWidget);
final messageWidget =
tester.widget<MessageWidget>(find.byType(MessageWidget));
expect(messageWidget.message, message);
expect(messageWidget.messageTheme, streamTheme.ownMessageTheme);
expect(messageWidget.attachmentBorderRadiusGeometry, null);
expect(messageWidget.showUserAvatar, DisplayWidget.show);
expect(messageWidget.reverse, false);
expect(messageWidget.attachmentShape, null);
expect(messageWidget.shape, null);
expect(messageWidget.onUserAvatarTap, null);
expect(messageWidget.showReactions, false);
expect(messageWidget.showUsername, false);
expect(messageWidget.showThreadReplyIndicator, false);
expect(messageWidget.showTimestamp, false);
expect(messageWidget.translateUserAvatar, false);
expect(messageWidget.showSendingIndicator, false);
expect(find.byType(ReactionBubble), findsNothing);
//only one avatar (the message one)
expect(find.byType(UserAvatar), findsOneWidget);
},
);
testWidgets(
'it should apply passed parameters',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
final message = Message(
id: 'test',
text: 'test message',
user: User(
id: 'test-user',
),
latestReactions: [
Reaction(
messageId: 'test',
user: User(id: 'testid'),
),
],
);
final onUserAvatarTap = (u) => print('ok');
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
client: client,
streamChatThemeData: streamTheme,
child: MessageReactionsModal(
message: message,
messageTheme: streamTheme.ownMessageTheme,
showUserAvatar: DisplayWidget.gone,
reverse: true,
attachmentBorderRadiusGeometry: BorderRadius.circular(1),
attachmentShape: RoundedRectangleBorder(),
showReactions: false,
messageShape: RoundedRectangleBorder(),
onUserAvatarTap: onUserAvatarTap,
),
),
),
);
await tester.pump(Duration(milliseconds: 1000));
expect(find.byType(MessageWidget), findsOneWidget);
final messageWidget =
tester.widget<MessageWidget>(find.byType(MessageWidget));
expect(messageWidget.message, message);
expect(messageWidget.messageTheme, streamTheme.ownMessageTheme);
expect(messageWidget.attachmentBorderRadiusGeometry,
BorderRadius.circular(1));
expect(messageWidget.showUserAvatar, DisplayWidget.gone);
expect(messageWidget.reverse, true);
expect(messageWidget.showReactions, false);
expect(messageWidget.attachmentShape, RoundedRectangleBorder());
expect(messageWidget.shape, RoundedRectangleBorder());
expect(messageWidget.showReactions, false);
expect(messageWidget.showUsername, false);
expect(messageWidget.showThreadReplyIndicator, false);
expect(messageWidget.showTimestamp, false);
expect(messageWidget.translateUserAvatar, false);
expect(messageWidget.showSendingIndicator, false);
final userAvatar = tester.widget<UserAvatar>(find.byType(UserAvatar));
expect(userAvatar.onTap, onUserAvatarTap);
expect(find.byType(ReactionBubble), findsOneWidget);
expect(find.byType(UserAvatar), findsOneWidget);
},
);
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show correct message text',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData);
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: MessageText(
message: Message(
text: 'demo',
),
messageTheme: streamTheme.otherMessageTheme),
),
),
),
));
expect(find.byType(MarkdownBody), findsOneWidget);
},
);
}
@@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -8,3 +9,9 @@ class MockClientState extends Mock implements ClientState {}
class MockChannel extends Mock implements Channel {}
class MockChannelState extends Mock implements ChannelClientState {}
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
class MockVoidCallback extends Mock {
void call();
}
@@ -1,18 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
import 'simple_frame.dart';
void main() {
testWidgets(
testGoldens(
'it should show no reactions',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: StreamChatTheme(
await tester.pumpWidgetBuilder(
SimpleFrame(
child: StreamChatTheme(
data: StreamChatThemeData(),
child: Container(
child: ReactionBubble(
@@ -24,15 +26,173 @@ void main() {
),
),
),
surfaceSize: Size(100, 100),
);
expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')),
findsNothing);
await screenMatchesGolden(tester, 'reaction_bubble_0');
},
);
testWidgets(
'it should show a like',
testGoldens(
'it should show a like - light theme',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData.light();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
final theme = StreamChatThemeData.getDefaultTheme(themeData);
await tester.pumpWidgetBuilder(
StreamChat(
client: client,
streamChatThemeData: theme,
child: Container(
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
],
borderColor: theme.ownMessageTheme.reactionsBorderColor,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor,
maskColor: theme.ownMessageTheme.reactionsMaskColor,
),
),
),
surfaceSize: Size(100, 100),
);
await screenMatchesGolden(tester, 'reaction_bubble_like_light');
},
);
testGoldens(
'it should show a like - dark theme',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData.dark();
final theme = StreamChatThemeData.getDefaultTheme(themeData);
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
color: Colors.black,
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
],
borderColor: theme.ownMessageTheme.reactionsBorderColor,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor,
maskColor: theme.ownMessageTheme.reactionsMaskColor,
),
),
),
surfaceSize: Size(100, 100),
);
await screenMatchesGolden(tester, 'reaction_bubble_like_dark');
},
);
testGoldens(
'it should show three reactions - light theme',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData.light();
final theme = StreamChatThemeData.getDefaultTheme(themeData);
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
color: Colors.black,
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
Reaction(
type: 'like',
user: User(id: 'user-id'),
),
Reaction(
type: 'like',
user: User(id: 'test'),
),
],
borderColor: theme.ownMessageTheme.reactionsBorderColor,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor,
maskColor: theme.ownMessageTheme.reactionsMaskColor,
),
),
),
surfaceSize: Size(140, 140),
);
await screenMatchesGolden(tester, 'reaction_bubble_3_light');
},
);
testGoldens(
'it should show three reactions - dark theme',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData.dark();
final theme = StreamChatThemeData.getDefaultTheme(themeData);
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder(
StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
color: Colors.black,
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
Reaction(
type: 'like',
user: User(id: 'user-id'),
),
Reaction(
type: 'like',
user: User(id: 'test'),
),
],
borderColor: theme.ownMessageTheme.reactionsBorderColor,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor,
maskColor: theme.ownMessageTheme.reactionsMaskColor,
),
),
),
surfaceSize: Size(140, 140),
);
await screenMatchesGolden(tester, 'reaction_bubble_3_dark');
},
);
testGoldens(
'it should show two reactions with customized ui',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
@@ -41,74 +201,40 @@ void main() {
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
],
borderColor: Colors.black,
backgroundColor: Colors.white,
maskColor: Colors.white,
),
await tester.pumpWidgetBuilder(
StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
Reaction(
type: 'love',
user: User(id: 'user-id'),
),
Reaction(
type: 'unknown',
user: User(id: 'test'),
),
],
borderColor: Colors.red,
backgroundColor: Colors.blue,
maskColor: Colors.green,
highlightOwnReactions: true,
reverse: true,
flipTail: true,
tailCirclesSpacing: 4,
),
),
),
surfaceSize: Size(200, 200),
);
expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')),
findsOneWidget);
},
);
testWidgets(
'it should show two reactions',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final themeData = ThemeData();
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: StreamChat(
client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData),
child: Container(
child: ReactionBubble(
reactions: [
Reaction(
type: 'like',
user: User(id: 'test'),
),
Reaction(
type: 'love',
user: User(id: 'test'),
),
],
borderColor: Colors.black,
backgroundColor: Colors.white,
maskColor: Colors.white,
),
),
),
),
);
expect(find.byKey(Key('StreamSvgIcon-Icon_thumbs_up_reaction.svg')),
findsOneWidget);
expect(find.byKey(Key('StreamSvgIcon-Icon_love_reaction.svg')),
findsOneWidget);
await screenMatchesGolden(tester, 'reaction_bubble_2');
},
);
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class SimpleFrame extends StatelessWidget {
final Widget child;
const SimpleFrame({Key key, @required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
border: Border.all(color: const Color(0xFF9E9E9E)),
),
child: child,
);
}
}
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show total unread count',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
var tapped = false;
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: SystemMessage(
onMessageTap: (m) => tapped = true,
message: Message(
text: 'demo message',
),
),
),
),
),
));
await tester.tap(find.byType(SystemMessage));
expect(find.text('demo message'), findsOneWidget);
expect(tapped, true);
},
);
testGoldens(
'control golden light',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
await tester.pumpWidgetBuilder(
materialAppWrapper(
theme: ThemeData.light(),
)(
StreamChat(
client: client,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Center(
child: SystemMessage(
message: Message(
text: 'demo message',
),
),
),
),
),
),
surfaceSize: Size.square(200),
);
await screenMatchesGolden(tester, 'system_message_light');
},
);
testGoldens(
'control golden dark',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
await tester.pumpWidgetBuilder(
materialAppWrapper(
theme: ThemeData.dark(),
)(
StreamChat(
client: client,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Center(
child: SystemMessage(
message: Message(
text: 'demo message',
),
),
),
),
),
),
surfaceSize: Size.square(200),
);
await screenMatchesGolden(tester, 'system_message_dark');
},
);
}
@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'control test',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ThreadHeader(
parent: Message(),
),
),
),
),
));
await tester.pumpAndSettle();
expect(find.text('with '), findsOneWidget);
expect(find.byType(ChannelName), findsOneWidget);
expect(find.byType(StreamBackButton), findsOneWidget);
expect(find.text('Thread Reply'), findsOneWidget);
},
);
testWidgets(
'it should apply passed props',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true));
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1));
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
var tapped = false;
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: ThreadHeader(
parent: Message(),
subtitle: Text('subtitle'),
leading: Text('leading'),
title: Text('title'),
onTitleTap: () {
tapped = true;
},
actions: [
Text('action'),
],
),
),
),
),
));
await tester.pumpAndSettle();
expect(find.text('title'), findsOneWidget);
await tester.tap(find.text('title'));
expect(tapped, true);
expect(find.text('subtitle'), findsOneWidget);
expect(find.text('action'), findsOneWidget);
expect(find.text('leading'), findsOneWidget);
},
);
}
@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show channel typing',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(channelState.membersStream).thenAnswer((i) => Stream.value([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
)
]));
when(channelState.members).thenReturn([
Member(
userId: 'user-id',
user: User(id: 'user-id'),
),
]);
when(channelState.messages).thenReturn([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]);
when(channelState.messagesStream).thenAnswer((i) => Stream.value([
Message(
text: 'hello',
user: User(id: 'other-user'),
)
]));
when(channelState.typingEvents).thenAnswer((i) => [
User(id: 'other-user', extraData: {'name': 'demo'})
]);
when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([
User(id: 'other-user', extraData: {'name': 'demo'}),
User(id: 'other-user', extraData: {'name': 'demo'}),
]));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: TypingIndicator(),
),
),
),
));
expect(find.byKey(Key('typings')), findsOneWidget);
},
);
}
@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart';
void main() {
testWidgets(
'it should show total unread count',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test',
}));
when(channel.extraData).thenReturn({
'name': 'test',
});
when(clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: UnreadIndicator(),
),
),
),
));
expect(find.text('10'), findsOneWidget);
},
);
testWidgets(
'it should show nothing if no unread messages',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
final channelState = MockChannelState();
when(channel.cid).thenReturn('cid');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(clientState.channels).thenReturn({
channel.cid: channel,
});
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channelState.unreadCount).thenReturn(0);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(0));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: UnreadIndicator(
cid: channel.cid,
),
),
),
),
));
expect(find.byType(SizedBox), findsOneWidget);
},
);
testWidgets(
'it should show 99+ if more than 99 unreads',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
final channel = MockChannel();
when(channel.cid).thenReturn('cid');
final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(clientState.channels).thenReturn({
channel.cid: channel,
});
when(channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client);
when(channelState.unreadCount).thenReturn(100);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(100));
await tester.pumpWidget(MaterialApp(
home: StreamChat(
client: client,
child: StreamChannel(
channel: channel,
child: Scaffold(
body: UnreadIndicator(
cid: channel.cid,
),
),
),
),
));
expect(find.text('99+'), findsOneWidget);
},
);
}