@@ -6,7 +6,7 @@ import 'package:checks/checks.dart';
6
6
import 'package:collection/collection.dart' ;
7
7
import 'package:fake_async/fake_async.dart' ;
8
8
import 'package:firebase_messaging/firebase_messaging.dart' ;
9
- import 'package:flutter/widgets.dart' ;
9
+ import 'package:flutter/widgets.dart' hide Notification ;
10
10
import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message, Person;
11
11
import 'package:flutter_test/flutter_test.dart' ;
12
12
import 'package:http/http.dart' as http;
@@ -74,6 +74,20 @@ MessageFcmMessage messageFcmMessage(
74
74
}) as MessageFcmMessage ;
75
75
}
76
76
77
+ RemoveFcmMessage removeFcmMessage (List <Message > zulipMessages, {Account ? account}) {
78
+ account ?? = eg.selfAccount;
79
+ return FcmMessage .fromJson ({
80
+ "event" : "remove" ,
81
+
82
+ "server" : "zulip.example.cloud" ,
83
+ "realm_id" : "4" ,
84
+ "realm_uri" : account.realmUrl.toString (),
85
+ "user_id" : account.userId.toString (),
86
+
87
+ "zulip_message_ids" : zulipMessages.map ((e) => e.id).join (',' ),
88
+ }) as RemoveFcmMessage ;
89
+ }
90
+
77
91
void main () {
78
92
TestZulipBinding .ensureInitialized ();
79
93
final zulipLocalizations = GlobalLocalizations .zulipLocalizations;
@@ -171,7 +185,10 @@ void main() {
171
185
..number.equals (messageStyleMessages.length)
172
186
..color.equals (kZulipBrandColor.value)
173
187
..smallIconResourceName.equals ('zulip_notification' )
174
- ..extras.isNull ()
188
+ ..extras.which ((it) => it.isNotNull ()
189
+ ..deepEquals (< String , String > {
190
+ NotificationDisplayManager .kExtraZulipMessageId: data.zulipMessageId.toString (),
191
+ }))
175
192
..groupKey.equals (expectedGroupKey)
176
193
..isGroupSummary.isNull ()
177
194
..inboxStyle.isNull ()
@@ -233,6 +250,28 @@ void main() {
233
250
async .flushMicrotasks ();
234
251
}
235
252
253
+ Condition <Object ?> conditionActiveNotif (MessageFcmMessage data, String tagComponent) {
254
+ final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
255
+ final expectedTag = '$expectedGroupKey |$tagComponent ' ;
256
+ return (it) => it.isA <StatusBarNotification >()
257
+ ..id.equals (NotificationDisplayManager .notificationIdAsHashOf (expectedTag))
258
+ ..notification.which ((it) => it
259
+ ..group.equals (expectedGroupKey)
260
+ ..extras.deepEquals (< String , String > {
261
+ NotificationDisplayManager .kExtraZulipMessageId: data.zulipMessageId.toString (),
262
+ }))
263
+ ..tag.equals (expectedTag);
264
+ }
265
+
266
+ Condition <Object ?> conditionSummaryActiveNotif (String expectedGroupKey) {
267
+ return (it) => it.isA <StatusBarNotification >()
268
+ ..id.equals (NotificationDisplayManager .notificationIdAsHashOf (expectedGroupKey))
269
+ ..notification.which ((it) => it
270
+ ..group.equals (expectedGroupKey)
271
+ ..extras.isEmpty ())
272
+ ..tag.equals (expectedGroupKey);
273
+ }
274
+
236
275
test ('stream message' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
237
276
await init ();
238
277
final stream = eg.stream ();
@@ -495,6 +534,112 @@ void main() {
495
534
expectedTitle: eg.selfUser.fullName,
496
535
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
497
536
})));
537
+
538
+ test ('remove: smoke' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
539
+ await init ();
540
+ final message = eg.streamMessage ();
541
+ final data = messageFcmMessage (message);
542
+ final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
543
+
544
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
545
+ await receiveFcmMessage (async , data);
546
+ check (testBinding.androidNotificationHost.activeNotifications).deepEquals (< Condition <Object ?>> [
547
+ conditionActiveNotif (data, 'stream:${message .streamId }:${message .topic }' ),
548
+ conditionSummaryActiveNotif (expectedGroupKey),
549
+ ]);
550
+ testBinding.firebaseMessaging.onMessage.add (
551
+ RemoteMessage (data: removeFcmMessage ([message]).toJson ()));
552
+ async .flushMicrotasks ();
553
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
554
+
555
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
556
+ await receiveFcmMessage (async , data);
557
+ check (testBinding.androidNotificationHost.activeNotifications).deepEquals (< Condition <Object ?>> [
558
+ conditionActiveNotif (data, 'stream:${message .streamId }:${message .topic }' ),
559
+ conditionSummaryActiveNotif (expectedGroupKey),
560
+ ]);
561
+ testBinding.firebaseMessaging.onBackgroundMessage.add (
562
+ RemoteMessage (data: removeFcmMessage ([message]).toJson ()));
563
+ async .flushMicrotasks ();
564
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
565
+ })));
566
+
567
+ test ('remove: clears conversation only if the removal event is for the last message' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
568
+ await init ();
569
+ final stream = eg.stream ();
570
+ const topicA = 'Topic A' ;
571
+ final message1 = eg.streamMessage (stream: stream, topic: topicA);
572
+ final data1 = messageFcmMessage (message1, streamName: stream.name);
573
+ final message2 = eg.streamMessage (stream: stream, topic: topicA);
574
+ final data2 = messageFcmMessage (message2, streamName: stream.name);
575
+ final message3 = eg.streamMessage (stream: stream, topic: topicA);
576
+ final data3 = messageFcmMessage (message3, streamName: stream.name);
577
+ final expectedGroupKey = '${data1 .realmUri }|${data1 .userId }' ;
578
+
579
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
580
+
581
+ await receiveFcmMessage (async , data1);
582
+ await receiveFcmMessage (async , data2);
583
+ await receiveFcmMessage (async , data3);
584
+
585
+ check (testBinding.androidNotificationHost.activeNotifications).deepEquals (< Condition <Object ?>> [
586
+ conditionActiveNotif (data3, 'stream:${stream .streamId }:$topicA ' ),
587
+ conditionSummaryActiveNotif (expectedGroupKey),
588
+ ]);
589
+
590
+ testBinding.firebaseMessaging.onMessage.add (
591
+ RemoteMessage (data: removeFcmMessage ([message1, message2]).toJson ()));
592
+ async .flushMicrotasks ();
593
+
594
+ check (testBinding.androidNotificationHost.activeNotifications).deepEquals (< Condition <Object ?>> [
595
+ conditionActiveNotif (data3, 'stream:${stream .streamId }:$topicA ' ),
596
+ conditionSummaryActiveNotif (expectedGroupKey),
597
+ ]);
598
+
599
+ testBinding.firebaseMessaging.onMessage.add (
600
+ RemoteMessage (data: removeFcmMessage ([message3]).toJson ()));
601
+ async .flushMicrotasks ();
602
+
603
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
604
+ })));
605
+
606
+ test ('remove: clears summary notification only if all conversation notifications are cleared' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
607
+ await init ();
608
+ final stream = eg.stream ();
609
+ const topicA = 'Topic A' ;
610
+ final message1 = eg.streamMessage (stream: stream, topic: topicA);
611
+ final data1 = messageFcmMessage (message1, streamName: stream.name);
612
+ const topicB = 'Topic B' ;
613
+ final message2 = eg.streamMessage (stream: stream, topic: topicB);
614
+ final data2 = messageFcmMessage (message2, streamName: stream.name);
615
+ final expectedGroupKey = '${data1 .realmUri }|${data1 .userId }' ;
616
+
617
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
618
+
619
+ await receiveFcmMessage (async , data1);
620
+ await receiveFcmMessage (async , data2);
621
+
622
+ check (testBinding.androidNotificationHost.activeNotifications).deepEquals (< Condition <Object ?>> [
623
+ conditionActiveNotif (data1, 'stream:${stream .streamId }:$topicA ' ),
624
+ conditionSummaryActiveNotif (expectedGroupKey),
625
+ conditionActiveNotif (data2, 'stream:${stream .streamId }:$topicB ' ),
626
+ ]);
627
+
628
+ testBinding.firebaseMessaging.onMessage.add (
629
+ RemoteMessage (data: removeFcmMessage ([message1]).toJson ()));
630
+ async .flushMicrotasks ();
631
+
632
+ check (testBinding.androidNotificationHost.activeNotifications).deepEquals (< Condition <Object ?>> [
633
+ conditionSummaryActiveNotif (expectedGroupKey),
634
+ conditionActiveNotif (data2, 'stream:${stream .streamId }:$topicB ' ),
635
+ ]);
636
+
637
+ testBinding.firebaseMessaging.onMessage.add (
638
+ RemoteMessage (data: removeFcmMessage ([message2]).toJson ()));
639
+ async .flushMicrotasks ();
640
+
641
+ check (testBinding.androidNotificationHost.activeNotifications).isEmpty ();
642
+ })));
498
643
});
499
644
500
645
group ('NotificationDisplayManager open' , () {
@@ -700,3 +845,14 @@ extension on Subject<MessagingStyleMessage> {
700
845
Subject <int > get timestampMs => has ((x) => x.timestampMs, 'timestampMs' );
701
846
Subject <Person > get person => has ((x) => x.person, 'person' );
702
847
}
848
+
849
+ extension on Subject <Notification > {
850
+ Subject <String > get group => has ((x) => x.group, 'group' );
851
+ Subject <Map <String ?, String ?>> get extras => has ((x) => x.extras, 'extras' );
852
+ }
853
+
854
+ extension on Subject <StatusBarNotification > {
855
+ Subject <int > get id => has ((x) => x.id, 'id' );
856
+ Subject <Notification > get notification => has ((x) => x.notification, 'notification' );
857
+ Subject <String > get tag => has ((x) => x.tag, 'tag' );
858
+ }
0 commit comments