Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5fd710f
test(e2e): add e2e_app, port native e2e suites, one app process per C…
testableapple Oct 23, 2026
0b8aa6d
chore(repo): ignore e2e mock server, Allure results and allurectl in …
testableapple Oct 23, 2026
b4979e4
test(repo): link every e2e test to its Allure TestOps case
testableapple Oct 23, 2026
b71afe2
Merge remote-tracking branch 'origin/master' into test/e2e-native-parity
testableapple Oct 23, 2026
b03e78e
test(repo): unmount the e2e app before resetting the client, verify c…
testableapple Oct 23, 2026
3bb8bd3
ci(repo): run PR e2e on API 36, use a preinstalled NDK, retry failed …
testableapple Oct 23, 2026
e7510b3
test(repo): retype into a field that ignored the text
testableapple Oct 23, 2026
1e75428
test(repo): keep the copied message id in the e2e app, bound the vid…
testableapple Oct 23, 2026
4b7a5fa
Merge branch 'master' into test/e2e-native-parity
testableapple Oct 23, 2026
8d9cef2
test(repo): scroll until a message is on screen, stop when the emulat…
testableapple Oct 23, 2026
0e4da80
ci(repo): give the e2e app's Gradle the sample app's memory settings
testableapple Oct 23, 2026
4a66a48
Bump timeout
testableapple Oct 24, 2026
ab90f31
test(repo): restart a dead emulator during the e2e run
testableapple Oct 24, 2026
c883599
test(repo): time-limit e2e runs so a dead emulator can't hang the job
testableapple Oct 24, 2026
87586b9
fix(repo): look up timeout through sh so the lane runs on Linux
testableapple Oct 24, 2026
d42a93a
ci(repo): split the e2e coverage nightly into 4 batches
testableapple Oct 25, 2026
8e73290
ci(repo): give the e2e runners memory headroom
testableapple Oct 25, 2026
b6acb2a
test(repo): retry the tap that opens the image gallery
testableapple Oct 25, 2026
b852d06
fix(ui): show system messages without a prefix in the channel list (F…
xsahil03x Oct 27, 2026
d0d5fad
test(repo): assert the token date and signature policy (#3123)
xsahil03x Oct 27, 2026
cb3b3fe
refactor(ui): decide the channel list delivery status up front (FLU-1…
xsahil03x Oct 27, 2026
bdf15fd
fix(ui): keep the footer of a sent message before a failed one (#3122)
xsahil03x Oct 27, 2026
e9e6266
Merge remote-tracking branch 'origin/test/e2e-native-parity' into sah…
xsahil03x Oct 27, 2026
a071835
Merge remote-tracking branch 'origin/master' into sahil/flu-1096-the-…
xsahil03x Oct 27, 2026
e87a28c
Merge remote-tracking branch 'origin/master' into sahil/flu-1096-the-…
xsahil03x Oct 28, 2026
ada2463
Merge remote-tracking branch 'origin/master' into sahil/flu-1096-the-…
xsahil03x Oct 28, 2026
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
6 changes: 1 addition & 5 deletions e2e_app/integration_test/channel_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,7 @@ void main() {
await env.userRobot.tapBackButton();

step('THEN the channel preview shows the system message');
// The mock server attributes the truncation to whoever asked for it, so
// the system message is the current user's and the preview carries the
// "You: " prefix — which the visual formatter applies even to a system
// message, unlike the a11y label.
await env.userRobot.assertMessageInChannelPreview(systemMessage, fromCurrentUser: true);
await env.userRobot.assertMessageInChannelPreview(systemMessage);

step('AND the message timestamp is shown');
await env.userRobot.assertMessagePreviewTimestamp();
Expand Down
1 change: 0 additions & 1 deletion e2e_app/integration_test/unread_counts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ void main() {
streamTestWithEnv(
allureId: '12832',
description: 'new system message increments the unread count',
skip: 'https://linear.app/stream/issue/FLU-1096',
body: (env) async {
const systemMessage = 'System message';

Expand Down
1 change: 1 addition & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

🐞 Fixed

- Fixed channel list previews showing a "You:" or sender name prefix, and the delivery status, in front of a system message.
- Fixed a deleted message hiding its replies count, which left its thread unreachable from the channel.
- Fixed `StreamMessageComposer` throwing when its channel is disposed while it still holds unsent text, for example when the user is disconnected with a channel page open.
- Fixed a message that failed to send, update or delete hiding the timestamp and delivery status of the message before it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,10 +695,11 @@ class _ChannelLastMessageWithStatusState extends State<_ChannelLastMessageWithSt
}

final isOwnMessage = currentUser != null && latestLastMessage.user?.id == currentUser.id;
final showsDeliveryStatus = isOwnMessage && !latestLastMessage.isDeleted && !latestLastMessage.isSystem;

// Show delivery status prefix only for own messages.
Widget? deliveryPrefix;
if (isOwnMessage) {
if (showsDeliveryStatus) {
if (widget.sendingIndicatorBuilder case final builder?) {
deliveryPrefix = builder(context, latestLastMessage);
} else {
Expand All @@ -714,7 +715,7 @@ class _ChannelLastMessageWithStatusState extends State<_ChannelLastMessageWithSt
return Row(
spacing: spacing.xxs,
children: [
if (!latestLastMessage.isDeleted) ?deliveryPrefix,
?deliveryPrefix,
Flexible(
child: StreamMessagePreviewText(
message: latestLastMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ abstract interface class MessagePreviewFormatter {
/// The output adapts to the message kind (regular, deleted, system, poll,
/// location) and to the surrounding [channel] — for example, a "You:"
/// prefix on the current user's messages, or "FirstName:" in group
/// channels.
/// channels. System messages show no prefix.
///
/// [showCaption] controls how attachment and location previews label
/// themselves: when `true` (the default) they display the message text; when
Expand Down Expand Up @@ -221,7 +221,7 @@ class StreamMessagePreviewFormatter implements AccessibleMessagePreviewFormatter
showCaption: showCaption,
);

if (channel == null) return content;
if (channel == null || message.isSystem) return content;

if (message.user?.id == currentUser?.id) {
return formatCurrentUserMessage(context, content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,42 @@ void main() {
);
});

testWidgets('group channel shows a system message from the current user without a prefix', (tester) async {
final channel = ChannelModel(
id: 'test-channel',
type: 'messaging',
memberCount: 3,
);

final message = Message(
text: 'Test User truncated the channel',
type: MessageType.system,
user: User(id: 'test-user-id', name: 'Test User'),
);

await pumpMessagePreview(tester, message, channel: channel);

expect(find.text('Test User truncated the channel'), findsOneWidget);
});

testWidgets('group channel shows a system message from another user without a prefix', (tester) async {
final channel = ChannelModel(
id: 'test-channel',
type: 'messaging',
memberCount: 3,
);

final message = Message(
text: 'Jane Doe added Maya',
type: MessageType.system,
user: User(id: 'other-user-id', name: 'Jane Doe'),
);

await pumpMessagePreview(tester, message, channel: channel);

expect(find.text('Jane Doe added Maya'), findsOneWidget);
});

testWidgets('group channel prepends the sender name in the themed sender style', (tester) async {
final channel = ChannelModel(
id: 'test-channel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,82 @@ void main() {
);
});

group('ChannelListTileSubtitle', () {
const currentUserId = 'me';

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

setUp(() {
client = MockClient();
final 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.channelState).thenReturn(const ChannelState());
});

// Pumps the channel list subtitle with [message] as the last message,
// drawing its delivery status as "sent".
Future<void> pumpSubtitle(WidgetTester tester, Message message) async {
when(() => channelState.messages).thenReturn([message]);
when(() => channelState.messagesStream).thenAnswer((_) => Stream.value([message]));
when(() => channelState.draftStream).thenAnswer((_) => const Stream.empty());
when(() => channelState.typingEventsStream).thenAnswer((_) => const Stream.empty());
when(() => channelState.typingEvents).thenReturn({});

await tester.pumpWidget(
MaterialApp(
home: StreamChat(
client: client,
child: Scaffold(
body: ChannelListTileSubtitle(
channel: channel,
sendingIndicatorBuilder: (_, _) => const Text('sent'),
),
),
),
),
);
await tester.pumpAndSettle();
}

testWidgets('shows the delivery status of a message from the current user', (tester) async {
final message = Message(
text: 'hello world',
user: User(id: currentUserId),
createdAt: DateTime(2024, 1, 1),
);

await pumpSubtitle(tester, message);

expect(find.text('sent'), findsOneWidget);
});

testWidgets('hides the delivery status of a system message from the current user', (tester) async {
final message = Message(
text: 'Me truncated the channel',
type: MessageType.system,
user: User(id: currentUserId),
createdAt: DateTime(2024, 1, 1),
);

await pumpSubtitle(tester, message);

expect(find.text('Me truncated the channel'), findsOneWidget);
expect(find.text('sent'), findsNothing);
});
});

group('ChannelLastMessageDate', () {
const currentUserId = 'me';

Expand Down
Loading