11import 'dart:convert' ;
2+ import 'dart:io' ;
23import 'dart:typed_data' ;
34
45import 'package:checks/checks.dart' ;
56import 'package:collection/collection.dart' ;
67import 'package:fake_async/fake_async.dart' ;
78import 'package:firebase_messaging/firebase_messaging.dart' ;
9+ import 'package:flutter/foundation.dart' ;
810import 'package:flutter/material.dart' ;
911import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message, Person;
1012import 'package:flutter_test/flutter_test.dart' ;
13+ import 'package:http/http.dart' as http;
14+ import 'package:http/testing.dart' as http_testing;
1115import 'package:zulip/api/model/model.dart' ;
1216import 'package:zulip/api/notifications.dart' ;
1317import 'package:zulip/host/android_notifications.dart' ;
@@ -25,6 +29,7 @@ import 'package:zulip/widgets/theme.dart';
2529import '../fake_async.dart' ;
2630import '../model/binding.dart' ;
2731import '../example_data.dart' as eg;
32+ import '../test_images.dart' ;
2833import '../test_navigation.dart' ;
2934import '../widgets/message_list_checks.dart' ;
3035import '../widgets/page_checks.dart' ;
@@ -79,6 +84,15 @@ void main() {
7984 TestZulipBinding .ensureInitialized ();
8085 final zulipLocalizations = GlobalLocalizations .zulipLocalizations;
8186
87+ final mockHttpClient = http_testing.MockClient (
88+ (request) async => http.Response .bytes (kSolidBlueAvatar, HttpStatus .ok));
89+
90+ void Function () run (Future <void > Function (FakeAsync async ) callback) {
91+ return () => http.runWithClient (() {
92+ return awaitFakeAsync (callback);
93+ }, () => mockHttpClient);
94+ }
95+
8296 Future <void > init () async {
8397 addTearDown (testBinding.reset);
8498 testBinding.firebaseMessagingInitialToken = '012abc' ;
@@ -135,7 +149,9 @@ void main() {
135149 ..text.equals (messageData.content)
136150 ..timestampMs.equals (messageData.time * 1000 )
137151 ..person.which ((it) => it.isNotNull ()
138- ..iconBitmap.which ((it) => isLast ? it.isNotNull () : it.isNull ())
152+ ..iconBitmap.which ((it) => isLast
153+ ? it.isNotNull ().deepEquals (kSolidBlueAvatar)
154+ : it.isNull ())
139155 ..key.equals (expectedSenderKey)
140156 ..name.equals (messageData.senderFullName));
141157 });
@@ -221,7 +237,7 @@ void main() {
221237 async .flushMicrotasks ();
222238 }
223239
224- test ('stream message' , () => awaitFakeAsync ((async ) async {
240+ test ('stream message' , run ((async ) async {
225241 await init ();
226242 final stream = eg.stream ();
227243 final message = eg.streamMessage (stream: stream);
@@ -231,7 +247,7 @@ void main() {
231247 expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
232248 }));
233249
234- test ('stream message: multiple messages, same topic' , () => awaitFakeAsync ((async ) async {
250+ test ('stream message: multiple messages, same topic' , run ((async ) async {
235251 await init ();
236252 final stream = eg.stream ();
237253 const topic = 'topic 1' ;
@@ -267,7 +283,67 @@ void main() {
267283 expectedTagComponent: expectedTagComponent);
268284 }));
269285
270- test ('stream message: stream name omitted' , () => awaitFakeAsync ((async ) async {
286+ test ('stream message: multiple messages, different topics' , run ((async ) async {
287+ await init ();
288+ final stream = eg.stream ();
289+ const topicA = 'topic A' ;
290+ const topicB = 'topic B' ;
291+ final message1 = eg.streamMessage (topic: topicA, stream: stream);
292+ final data1 = messageFcmMessage (message1, streamName: stream.name);
293+ final message2 = eg.streamMessage (topic: topicB, stream: stream);
294+ final data2 = messageFcmMessage (message2, streamName: stream.name);
295+ final message3 = eg.streamMessage (topic: topicA, stream: stream);
296+ final data3 = messageFcmMessage (message3, streamName: stream.name);
297+
298+ await receiveFcmMessage (async , data1);
299+ checkNotification (data1,
300+ messageStyleMessages: [data1],
301+ expectedIsGroupConversation: true ,
302+ expectedTitle: '#${stream .name } > $topicA ' ,
303+ expectedTagComponent: 'stream:${stream .streamId }:$topicA ' );
304+
305+ await receiveFcmMessage (async , data2);
306+ checkNotification (data2,
307+ messageStyleMessages: [data2],
308+ expectedIsGroupConversation: true ,
309+ expectedTitle: '#${stream .name } > $topicB ' ,
310+ expectedTagComponent: 'stream:${stream .streamId }:$topicB ' );
311+
312+ await receiveFcmMessage (async , data3);
313+ checkNotification (data3,
314+ messageStyleMessages: [data1, data3],
315+ expectedIsGroupConversation: true ,
316+ expectedTitle: '#${stream .name } > $topicA ' ,
317+ expectedTagComponent: 'stream:${stream .streamId }:$topicA ' );
318+ }));
319+
320+ test ('stream message: conversation stays same when stream is renamed' , run ((async ) async {
321+ await init ();
322+ var stream = eg.stream (streamId: 1 , name: 'Before' );
323+ const topic = 'topic' ;
324+ final message1 = eg.streamMessage (topic: topic, stream: stream);
325+ final data1 = messageFcmMessage (message1, streamName: stream.name);
326+
327+ await receiveFcmMessage (async , data1);
328+ checkNotification (data1,
329+ messageStyleMessages: [data1],
330+ expectedIsGroupConversation: true ,
331+ expectedTitle: '#Before > $topic ' ,
332+ expectedTagComponent: 'stream:${stream .streamId }:$topic ' );
333+
334+ stream = eg.stream (streamId: 1 , name: 'After' );
335+ final message2 = eg.streamMessage (topic: topic, stream: stream);
336+ final data2 = messageFcmMessage (message2, streamName: stream.name);
337+
338+ await receiveFcmMessage (async , data2);
339+ checkNotification (data2,
340+ messageStyleMessages: [data1, data2],
341+ expectedIsGroupConversation: true ,
342+ expectedTitle: '#After > $topic ' ,
343+ expectedTagComponent: 'stream:${stream .streamId }:$topic ' );
344+ }));
345+
346+ test ('stream message: stream name omitted' , () => run ((async ) async {
271347 await init ();
272348 final stream = eg.stream ();
273349 final message = eg.streamMessage (stream: stream);
@@ -277,7 +353,7 @@ void main() {
277353 expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
278354 }));
279355
280- test ('group DM: 3 users' , () => awaitFakeAsync ((async ) async {
356+ test ('group DM: 3 users' , () => run ((async ) async {
281357 await init ();
282358 final message = eg.dmMessage (from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
283359 await checkNotifications (async , messageFcmMessage (message),
@@ -286,7 +362,7 @@ void main() {
286362 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
287363 }));
288364
289- test ('group DM: more than 3 users' , () => awaitFakeAsync ((async ) async {
365+ test ('group DM: more than 3 users' , () => run ((async ) async {
290366 await init ();
291367 final message = eg.dmMessage (from: eg.thirdUser,
292368 to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
@@ -296,7 +372,31 @@ void main() {
296372 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
297373 }));
298374
299- test ('1:1 DM' , () => awaitFakeAsync ((async ) async {
375+ test ('group DM: title updates with latest sender' , () => run ((async ) async {
376+ await init ();
377+ final message1 = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser, eg.thirdUser]);
378+ final data1 = messageFcmMessage (message1);
379+ final message2 = eg.dmMessage (from: eg.thirdUser, to: [eg.selfUser, eg.otherUser]);
380+ final data2 = messageFcmMessage (message2);
381+
382+ final expectedTagComponent = 'dm:${message1 .allRecipientIds .join ("," )}' ;
383+
384+ await receiveFcmMessage (async , data1);
385+ checkNotification (data1,
386+ messageStyleMessages: [data1],
387+ expectedIsGroupConversation: true ,
388+ expectedTitle: "${eg .otherUser .fullName } to you and 1 other" ,
389+ expectedTagComponent: expectedTagComponent);
390+
391+ await receiveFcmMessage (async , data2);
392+ checkNotification (data2,
393+ messageStyleMessages: [data1, data2],
394+ expectedIsGroupConversation: true ,
395+ expectedTitle: "${eg .thirdUser .fullName } to you and 1 other" ,
396+ expectedTagComponent: expectedTagComponent);
397+ }));
398+
399+ test ('1:1 DM' , () => run ((async ) async {
300400 await init ();
301401 final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
302402 await checkNotifications (async , messageFcmMessage (message),
@@ -305,7 +405,61 @@ void main() {
305405 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
306406 }));
307407
308- test ('self-DM' , () => awaitFakeAsync ((async ) async {
408+ test ('1:1 DM: title updates when sender name changes' , () => run ((async ) async {
409+ await init ();
410+ final otherUser = eg.user (fullName: 'Before' );
411+ final message1 = eg.dmMessage (from: otherUser, to: [eg.selfUser]);
412+ final data1 = messageFcmMessage (message1);
413+
414+ final expectedTagComponent = 'dm:${message1 .allRecipientIds .join ("," )}' ;
415+
416+ await receiveFcmMessage (async , data1);
417+ checkNotification (data1,
418+ messageStyleMessages: [data1],
419+ expectedIsGroupConversation: false ,
420+ expectedTitle: 'Before' ,
421+ expectedTagComponent: expectedTagComponent);
422+
423+ otherUser.fullName = 'After' ;
424+ final message2 = eg.dmMessage (from: otherUser, to: [eg.selfUser]);
425+ final data2 = messageFcmMessage (message2);
426+
427+ await receiveFcmMessage (async , data2);
428+ checkNotification (data2,
429+ messageStyleMessages: [data1, data2],
430+ expectedIsGroupConversation: false ,
431+ expectedTitle: 'After' ,
432+ expectedTagComponent: expectedTagComponent);
433+ }));
434+
435+ test ('1:1 DM: conversation stays same when sender email changes' , () => run ((async ) async {
436+ await init ();
437+ final otherUser
= eg.
user (email
: '[email protected] ' );
438+ final message1 = eg.dmMessage (from: otherUser, to: [eg.selfUser]);
439+ final data1 = messageFcmMessage (message1);
440+
441+ final expectedTagComponent = 'dm:${message1 .allRecipientIds .join ("," )}' ;
442+
443+ await receiveFcmMessage (async , data1);
444+ checkNotification (data1,
445+ messageStyleMessages: [data1],
446+ expectedIsGroupConversation: false ,
447+ expectedTitle: otherUser.fullName,
448+ expectedTagComponent: expectedTagComponent);
449+
450+ otherUser.email
= '[email protected] ' ;
451+ final message2 = eg.dmMessage (from: otherUser, to: [eg.selfUser]);
452+ final data2 = messageFcmMessage (message2);
453+
454+ await receiveFcmMessage (async , data2);
455+ checkNotification (data2,
456+ messageStyleMessages: [data1, data2],
457+ expectedIsGroupConversation: false ,
458+ expectedTitle: otherUser.fullName,
459+ expectedTagComponent: expectedTagComponent);
460+ }));
461+
462+ test ('self-DM' , () => run ((async ) async {
309463 await init ();
310464 final message = eg.dmMessage (from: eg.selfUser, to: []);
311465 await checkNotifications (async , messageFcmMessage (message),
0 commit comments