@@ -2,12 +2,15 @@ import 'package:checks/checks.dart';
2
2
import 'package:flutter_test/flutter_test.dart' ;
3
3
import 'package:zulip/api/model/model.dart' ;
4
4
import 'package:zulip/model/recent_senders.dart' ;
5
+ import 'package:zulip/model/store.dart' ;
5
6
import '../example_data.dart' as eg;
7
+ import 'test_store.dart' ;
6
8
7
9
/// [messages] should be sorted by [id] ascending.
8
10
void checkMatchesMessages (RecentSenders model, List <Message > messages) {
9
11
final Map <int , Map <int , Set <int >>> messagesByUserInStream = {};
10
12
final Map <int , Map <TopicName , Map <int , Set <int >>>> messagesByUserInTopic = {};
13
+ messages.sort ((a, b) => a.id - b.id);
11
14
for (final message in messages) {
12
15
if (message is ! StreamMessage ) {
13
16
throw UnsupportedError ('Message of type ${message .runtimeType } is not expected.' );
@@ -142,6 +145,156 @@ void main() {
142
145
});
143
146
});
144
147
148
+ group ('RecentSenders.handleUpdateMessageEvent' , () {
149
+ late PerAccountStore store;
150
+ late RecentSenders model;
151
+
152
+ final origChannel = eg.stream (); final newChannel = eg.stream ();
153
+ final origTopic = 'origTopic' ; final newTopic = 'newTopic' ;
154
+ final userX = eg.user (); final userY = eg.user ();
155
+
156
+ Future <void > prepare (List <Message > messages) async {
157
+ store = eg.store ();
158
+ await store.addMessages (messages);
159
+ await store.addStreams ([origChannel, newChannel]);
160
+ await store.addUsers ([userX, userY]);
161
+ model = store.recentSenders;
162
+ }
163
+
164
+ List <StreamMessage > copyMessagesWith (Iterable <StreamMessage > messages, {
165
+ ZulipStream ? newChannel,
166
+ String ? newTopic,
167
+ }) {
168
+ assert (newChannel != null || newTopic != null );
169
+ return messages.map ((message) => StreamMessage .fromJson (
170
+ message.toJson ()
171
+ ..['stream_id' ] = newChannel? .streamId ?? message.streamId
172
+ // See [StreamMessage.displayRecipient] for why this is needed.
173
+ ..['display_recipient' ] = newChannel? .name ?? message.displayRecipient!
174
+
175
+ ..['subject' ] = newTopic ?? message.topic
176
+ )).toList ();
177
+ }
178
+
179
+ test ('move a conversation entirely, with additional unknown messages' , () async {
180
+ final messages = List .generate (10 , (i) => eg.streamMessage (
181
+ stream: origChannel, topic: origTopic, sender: userX));
182
+ await prepare (messages);
183
+ final unknownMessages = List .generate (10 , (i) => eg.streamMessage (
184
+ stream: origChannel, topic: origTopic, sender: userX));
185
+ checkMatchesMessages (model, messages);
186
+
187
+ final messageIdsByUserInTopicBefore =
188
+ model.topicSenders[origChannel.streamId]! [eg.t (origTopic)]! [userX.userId]! .ids;
189
+
190
+ await store.handleEvent (eg.updateMessageEventMoveFrom (
191
+ origMessages: messages + unknownMessages,
192
+ newStreamId: newChannel.streamId));
193
+ checkMatchesMessages (model, copyMessagesWith (
194
+ messages, newChannel: newChannel));
195
+
196
+ // Check we avoided creating a new list for the moved message IDs.
197
+ check (messageIdsByUserInTopicBefore).identicalTo (
198
+ model.topicSenders[newChannel.streamId]! [eg.t (origTopic)]! [userX.userId]! .ids);
199
+ });
200
+
201
+ test ('move a conversation exactly' , () async {
202
+ final messages = List .generate (10 , (i) => eg.streamMessage (
203
+ stream: origChannel, topic: origTopic, sender: userX));
204
+ await prepare (messages);
205
+
206
+ final messageIdsByUserInTopicBefore =
207
+ model.topicSenders[origChannel.streamId]! [eg.t (origTopic)]! [userX.userId]! .ids;
208
+
209
+ await store.handleEvent (eg.updateMessageEventMoveFrom (
210
+ origMessages: messages,
211
+ newStreamId: newChannel.streamId,
212
+ newTopicStr: newTopic));
213
+ checkMatchesMessages (model, copyMessagesWith (
214
+ messages, newChannel: newChannel, newTopic: newTopic));
215
+
216
+ // Check we avoided creating a new list for the moved message IDs.
217
+ check (messageIdsByUserInTopicBefore).identicalTo (
218
+ model.topicSenders[newChannel.streamId]! [eg.t (newTopic)]! [userX.userId]! .ids);
219
+ });
220
+
221
+ test ('move a conversation partially to a different channel' , () async {
222
+ final messages = List .generate (10 , (i) => eg.streamMessage (
223
+ stream: origChannel, topic: origTopic));
224
+ final movedMessages = messages.take (5 ).toList ();
225
+ final otherMessages = messages.skip (5 );
226
+ await prepare (messages);
227
+
228
+ await store.handleEvent (eg.updateMessageEventMoveFrom (
229
+ origMessages: movedMessages,
230
+ newStreamId: newChannel.streamId));
231
+ checkMatchesMessages (model, [
232
+ ...copyMessagesWith (movedMessages, newChannel: newChannel),
233
+ ...otherMessages,
234
+ ]);
235
+ });
236
+
237
+ test ('move a conversation partially to a different topic, within the same channel' , () async {
238
+ final messages = List .generate (10 , (i) => eg.streamMessage (
239
+ stream: origChannel, topic: origTopic, sender: userX));
240
+ final movedMessages = messages.take (5 ).toList ();
241
+ final otherMessages = messages.skip (5 );
242
+ await prepare (messages);
243
+
244
+ final messageIdsByUserInStreamBefore =
245
+ model.streamSenders[origChannel.streamId]! [userX.userId]! .ids;
246
+
247
+ await store.handleEvent (eg.updateMessageEventMoveFrom (
248
+ origMessages: movedMessages,
249
+ newTopicStr: newTopic));
250
+ checkMatchesMessages (model, [
251
+ ...copyMessagesWith (movedMessages, newTopic: newTopic),
252
+ ...otherMessages,
253
+ ]);
254
+
255
+ // Check that we did not touch stream message IDs tracker
256
+ // when there wasn't a stream move.
257
+ check (messageIdsByUserInStreamBefore).identicalTo (
258
+ model.streamSenders[origChannel.streamId]! [userX.userId]! .ids);
259
+ });
260
+
261
+ test ('move a conversation with multiple senders' , () async {
262
+ final messages = [
263
+ eg.streamMessage (stream: origChannel, topic: origTopic, sender: userX),
264
+ eg.streamMessage (stream: origChannel, topic: origTopic, sender: userX),
265
+ eg.streamMessage (stream: origChannel, topic: origTopic, sender: userY),
266
+ ];
267
+ await prepare (messages);
268
+
269
+ await store.handleEvent (eg.updateMessageEventMoveFrom (
270
+ origMessages: messages,
271
+ newStreamId: newChannel.streamId));
272
+ checkMatchesMessages (model, copyMessagesWith (
273
+ messages, newChannel: newChannel));
274
+ });
275
+
276
+ test ('move a converstion, but message IDs from the event are not sorted in ascending order' , () async {
277
+ final messages = List .generate (10 , (i) => eg.streamMessage (
278
+ id: 100 - i, stream: origChannel, topic: origTopic));
279
+ await prepare (messages);
280
+
281
+ await store.handleEvent (eg.updateMessageEventMoveFrom (
282
+ origMessages: messages,
283
+ newStreamId: newChannel.streamId));
284
+ checkMatchesMessages (model,
285
+ copyMessagesWith (messages, newChannel: newChannel));
286
+ });
287
+
288
+ test ('message edit update without move' , () async {
289
+ final messages = List .generate (10 , (i) => eg.streamMessage (
290
+ stream: origChannel, topic: origTopic));
291
+ await prepare (messages);
292
+
293
+ await store.handleEvent (eg.updateMessageEditEvent (messages[0 ]));
294
+ checkMatchesMessages (model, messages);
295
+ });
296
+ });
297
+
145
298
test ('RecentSenders.handleDeleteMessageEvent' , () {
146
299
final model = RecentSenders ();
147
300
final stream = eg.stream ();
0 commit comments