@@ -5,11 +5,13 @@ import 'package:zulip/api/model/events.dart';
5
5
import 'package:zulip/api/model/initial_snapshot.dart' ;
6
6
import 'package:zulip/api/model/model.dart' ;
7
7
import 'package:zulip/model/algorithms.dart' ;
8
+ import 'package:zulip/model/channel.dart' ;
8
9
import 'package:zulip/model/narrow.dart' ;
9
10
import 'package:zulip/model/store.dart' ;
10
11
import 'package:zulip/model/unreads.dart' ;
11
12
12
13
import '../example_data.dart' as eg;
14
+ import '../stdlib_checks.dart' ;
13
15
import 'test_store.dart' ;
14
16
import 'unreads_checks.dart' ;
15
17
@@ -76,7 +78,7 @@ void main() {
76
78
assert (Set .of (messages.map ((m) => m.id)).length == messages.length,
77
79
'checkMatchesMessages: duplicate messages in test input' );
78
80
79
- final Map <int , Map < TopicName , QueueList <int >>> expectedStreams = {};
81
+ final Map <int , TopicKeyedMap < QueueList <int >>> expectedStreams = {};
80
82
final Map <DmNarrow , QueueList <int >> expectedDms = {};
81
83
final Set <int > expectedMentions = {};
82
84
for (final message in messages) {
@@ -85,7 +87,7 @@ void main() {
85
87
}
86
88
switch (message) {
87
89
case StreamMessage ():
88
- final perTopic = expectedStreams[message.streamId] ?? = {} ;
90
+ final perTopic = expectedStreams[message.streamId] ?? = makeTopicKeyedMap () ;
89
91
final messageIds = perTopic[message.topic] ?? = QueueList ();
90
92
messageIds.add (message.id);
91
93
case DmMessage ():
@@ -136,6 +138,9 @@ void main() {
136
138
eg.unreadChannelMsgs (streamId: stream1.streamId, topic: 'b' , unreadMessageIds: [3 , 4 ]),
137
139
eg.unreadChannelMsgs (streamId: stream2.streamId, topic: 'b' , unreadMessageIds: [5 , 6 ]),
138
140
eg.unreadChannelMsgs (streamId: stream2.streamId, topic: 'c' , unreadMessageIds: [7 , 8 ]),
141
+
142
+ // TODO(server-10) drop this (see implementation)
143
+ eg.unreadChannelMsgs (streamId: stream2.streamId, topic: 'C' , unreadMessageIds: [9 , 10 ]),
139
144
],
140
145
dms: [
141
146
UnreadDmSnapshot (otherUserId: 1 , unreadMessageIds: [11 , 12 ]),
@@ -157,6 +162,8 @@ void main() {
157
162
eg.streamMessage (id: 6 , stream: stream2, topic: 'b' , flags: [MessageFlag .mentioned]),
158
163
eg.streamMessage (id: 7 , stream: stream2, topic: 'c' , flags: []),
159
164
eg.streamMessage (id: 8 , stream: stream2, topic: 'c' , flags: []),
165
+ eg.streamMessage (id: 9 , stream: stream2, topic: 'C' , flags: []),
166
+ eg.streamMessage (id: 10 , stream: stream2, topic: 'C' , flags: []),
160
167
eg.dmMessage (id: 11 , from: user1, to: [eg.selfUser], flags: []),
161
168
eg.dmMessage (id: 12 , from: user1, to: [eg.selfUser], flags: []),
162
169
eg.dmMessage (id: 13 , from: user2, to: [eg.selfUser], flags: []),
@@ -201,10 +208,10 @@ void main() {
201
208
await store.addUserTopic (stream, 'c' , UserTopicVisibilityPolicy .muted);
202
209
fillWithMessages ([
203
210
eg.streamMessage (stream: stream, topic: 'a' , flags: []),
204
- eg.streamMessage (stream: stream, topic: 'a' , flags: []),
205
- eg.streamMessage (stream: stream, topic: 'b' , flags: []),
211
+ eg.streamMessage (stream: stream, topic: 'A' , flags: []),
206
212
eg.streamMessage (stream: stream, topic: 'b' , flags: []),
207
213
eg.streamMessage (stream: stream, topic: 'b' , flags: []),
214
+ eg.streamMessage (stream: stream, topic: 'B' , flags: []),
208
215
eg.streamMessage (stream: stream, topic: 'c' , flags: []),
209
216
]);
210
217
check (model.countInChannel (stream.streamId)).equals (5 );
@@ -220,9 +227,13 @@ void main() {
220
227
test ('countInTopicNarrow' , () {
221
228
final stream = eg.stream ();
222
229
prepare ();
223
- fillWithMessages (List .generate (7 , (i) => eg.streamMessage (
224
- stream: stream, topic: 'a' , flags: [])));
225
- check (model.countInTopicNarrow (stream.streamId, eg.t ('a' ))).equals (7 );
230
+ final messages = [
231
+ ...List .generate (7 , (i) => eg.streamMessage (stream: stream, topic: 'a' , flags: [])),
232
+ ...List .generate (2 , (i) => eg.streamMessage (stream: stream, topic: 'A' , flags: [])),
233
+ ];
234
+ fillWithMessages (messages);
235
+ check (model.countInTopicNarrow (stream.streamId, eg.t ('a' ))).equals (9 );
236
+ check (model.countInTopicNarrow (stream.streamId, eg.t ('A' ))).equals (9 );
226
237
});
227
238
228
239
test ('countInDmNarrow' , () {
@@ -370,6 +381,24 @@ void main() {
370
381
});
371
382
}
372
383
});
384
+
385
+ test ('topics case-insensitive but case-preserving' , () {
386
+ final stream = eg.stream ();
387
+ final message1 = eg.streamMessage (stream: stream, topic: 'aaa' );
388
+ final message2 = eg.streamMessage (stream: stream, topic: 'AaA' );
389
+ final message3 = eg.streamMessage (stream: stream, topic: 'aAa' );
390
+ prepare ();
391
+ fillWithMessages ([message1]);
392
+ model.handleMessageEvent (eg.messageEvent (message2));
393
+ model.handleMessageEvent (eg.messageEvent (message3));
394
+ checkNotified (count: 2 );
395
+ checkMatchesMessages ([message1, message2, message3]);
396
+ // Redundant with checkMatchesMessages, but for explicitness here:
397
+ check (model).streams.values.single
398
+ .entries.single
399
+ ..key.equals (eg.t ('aaa' ))
400
+ ..value.length.equals (3 );
401
+ });
373
402
});
374
403
375
404
group ('DM messages' , () {
@@ -629,6 +658,39 @@ void main() {
629
658
checkMatchesMessages (copyMessagesWith (unreadMessages, newTopic: newTopic));
630
659
});
631
660
661
+ test ('topics case-insensitive but case-preserving' , () async {
662
+ final message1 = eg.streamMessage (stream: origChannel, topic: 'aaa' , flags: []);
663
+ final message2 = eg.streamMessage (stream: origChannel, topic: 'aaa' , flags: []);
664
+ final messages = [message1, message2];
665
+ await prepareStore ();
666
+ fillWithMessages (messages);
667
+
668
+ model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
669
+ // 'AAA' finds the key 'aaa'
670
+ origMessages: copyMessagesWith ([message1], newTopic: 'AAA' ),
671
+ newTopicStr: 'bbb' ));
672
+ checkNotifiedOnce ();
673
+ checkMatchesMessages ([
674
+ ...copyMessagesWith ([message1], newTopic: 'bbb' ),
675
+ message2,
676
+ ]);
677
+
678
+ model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
679
+ origMessages: [message2],
680
+ // 'BBB' finds the key 'bbb'
681
+ newTopicStr: 'BBB' ));
682
+ checkNotifiedOnce ();
683
+ checkMatchesMessages ([
684
+ ...copyMessagesWith ([message1], newTopic: 'bbb' ),
685
+ ...copyMessagesWith ([message2], newTopic: 'BBB' ),
686
+ ]);
687
+ // Redundant with checkMatchesMessages, but for explicitness here:
688
+ check (model).streams.values.single
689
+ .entries.single
690
+ ..key.equals (eg.t ('bbb' ))
691
+ ..value.length.equals (2 );
692
+ });
693
+
632
694
test ('tolerates unreads unknown to the model' , () async {
633
695
await prepareStore ();
634
696
fillWithMessages (unreadMessages);
@@ -690,14 +752,20 @@ void main() {
690
752
fillWithMessages (messages);
691
753
692
754
final expectedRemainingMessages = Set .of (messages);
755
+ assert (messages.any ((m) => m.id == 14 ));
693
756
for (final message in messages) {
694
757
final event = switch (message) {
695
758
StreamMessage () => DeleteMessageEvent (
696
759
id: 0 ,
697
760
messageType: MessageType .stream,
698
761
messageIds: [message.id],
699
762
streamId: message.streamId,
700
- topic: message.topic,
763
+ topic: () {
764
+ if (message.id != 14 ) return message.topic;
765
+ final uppercase = message.topic.apiName.toUpperCase ();
766
+ assert (message.topic.apiName != uppercase);
767
+ return eg.t (uppercase); // exercise case-insensitivity of topics
768
+ }(),
701
769
),
702
770
DmMessage () => DeleteMessageEvent (
703
771
id: 0 ,
0 commit comments