Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Upcoming

🐞 Fixed
- Fixed shadowed messages not hidden in channel list items.

## 9.26.0

🐞 Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,15 @@ class ChannelLastMessageText extends StatefulWidget {
super.key,
required this.channel,
this.textStyle,
this.lastMessagePredicate = _defaultLastMessagePredicate,
}) : assert(
bool Function(Message)? lastMessagePredicate,
}) : assert(
channel.state != null,
'Channel ${channel.id} is not initialized',
);
),
lastMessagePredicate = lastMessagePredicate ??
_defaultLastMessagePredicate(
channel.client.state.currentUser?.id,
);

/// The channel to display the last message of.
final Channel channel;
Expand All @@ -368,15 +372,21 @@ class ChannelLastMessageText extends StatefulWidget {
/// considered for the last message.
final bool Function(Message) lastMessagePredicate;

// The default predicate to determine if the message should be
// considered for the last message.
static bool _defaultLastMessagePredicate(Message message) {
if (message.isShadowed) return false;
if (message.isDeleted) return false;
if (message.isError) return false;
if (message.isEphemeral) return false;

return true;
// The default predicate to determine if the message should be considered
// for the last message. Hides shadowed messages from non-senders so the
// sender's preview matches the messaging UI.
static bool Function(Message) _defaultLastMessagePredicate(
String? currentUserId,
) {
return (Message message) {
final isMyMessage =
currentUserId != null && message.user?.id == currentUserId;
if (message.shadowed && !isMyMessage) return false;
if (message.isDeleted) return false;
if (message.isError) return false;
if (message.isEphemeral) return false;
return true;
};
}

@override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

import '../../mocks.dart';

void main() {
const currentUserId = 'me';

late MockClient client;
late MockClientState clientState;
late MockChannel channel;
late MockChannelState channelState;

setUp(() {
client = MockClient();
clientState = MockClientState();
channel = MockChannel();
channelState = MockChannelState();

final currentUser = OwnUser(id: currentUserId, name: 'Me');

when(() => client.state).thenReturn(clientState);
when(() => clientState.currentUser).thenReturn(currentUser);
when(() => clientState.currentUserStream)
.thenAnswer((_) => Stream.value(currentUser));
when(() => channel.state).thenReturn(channelState);
when(() => channel.client).thenReturn(client);
when(() => channelState.draft).thenReturn(null);
when(() => channelState.draftStream).thenAnswer((_) => Stream.value(null));
when(() => channelState.channelState).thenReturn(const ChannelState());
});

Future<void> pumpWithMessages(
WidgetTester tester,
List<Message> messages,
) async {
when(() => channelState.messages).thenReturn(messages);
when(() => channelState.messagesStream)
.thenAnswer((_) => Stream.value(messages));

await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelLastMessageText(channel: channel),
),
),
),
);
await tester.pumpAndSettle();
}

testWidgets('shows the latest message text as preview', (tester) async {
final message = Message(
text: 'hello world',
user: User(id: 'other'),
createdAt: DateTime(2024),
);

await pumpWithMessages(tester, [message]);

expect(find.text('hello world'), findsOneWidget);
});

testWidgets('hides shadowed message from another user', (tester) async {
final visible = Message(
text: 'visible',
user: User(id: 'other'),
createdAt: DateTime(2024),
);
final shadowed = Message(
text: 'shadowed',
shadowed: true,
user: User(id: 'other'),
createdAt: DateTime(2024, 1, 2),
);

await pumpWithMessages(tester, [visible, shadowed]);

expect(find.text('visible'), findsOneWidget);
expect(find.text('shadowed'), findsNothing);
});

testWidgets(
'shows current user own shadowed message',
(tester) async {
final mine = Message(
text: 'my shadowed',
shadowed: true,
user: User(id: currentUserId),
createdAt: DateTime(2024),
);

await pumpWithMessages(tester, [mine]);

expect(find.text('my shadowed'), findsOneWidget);
},
);

testWidgets('hides deleted, error, and ephemeral messages', (tester) async {
final visible = Message(
text: 'visible',
user: User(id: 'other'),
createdAt: DateTime(2024),
);
final deleted = Message(
text: 'deleted',
type: MessageType.deleted,
user: User(id: 'other'),
createdAt: DateTime(2024, 1, 2),
);
final errored = Message(
text: 'errored',
type: MessageType.error,
user: User(id: 'other'),
createdAt: DateTime(2024, 1, 3),
);
final ephemeral = Message(
text: 'ephemeral',
type: MessageType.ephemeral,
user: User(id: 'other'),
createdAt: DateTime(2024, 1, 4),
);

await pumpWithMessages(tester, [visible, deleted, errored, ephemeral]);

expect(find.text('visible'), findsOneWidget);
expect(find.text('deleted'), findsNothing);
expect(find.text('errored'), findsNothing);
expect(find.text('ephemeral'), findsNothing);
});

testWidgets(
'custom lastMessagePredicate fully replaces the default',
(tester) async {
final shadowedByOther = Message(
text: 'shadowed by other',
shadowed: true,
user: User(id: 'other'),
createdAt: DateTime(2024),
);

when(() => channelState.messages).thenReturn([shadowedByOther]);
when(() => channelState.messagesStream)
.thenAnswer((_) => Stream.value([shadowedByOther]));

await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelLastMessageText(
channel: channel,
lastMessagePredicate: (_) => true,
),
),
),
),
);
await tester.pumpAndSettle();

expect(find.text('shadowed by other'), findsOneWidget);
},
);
}
Loading