@@ -270,6 +270,179 @@ void main() {
270
270
check (model).fetched.isFalse ();
271
271
});
272
272
273
+ group ('UserTopicEvent' , () {
274
+ // The StreamStore.willChangeIfTopicVisible/InStream methods have their own
275
+ // thorough unit tests. So these tests focus on the rest of the logic.
276
+
277
+ final stream = eg.stream ();
278
+ const String topic = 'foo' ;
279
+
280
+ Future <void > setVisibility (UserTopicVisibilityPolicy policy) async {
281
+ await store.handleEvent (eg.userTopicEvent (stream.streamId, topic, policy));
282
+ }
283
+
284
+ /// (Should run after `prepare` .)
285
+ Future <void > prepareMutes ([
286
+ bool streamMuted = false ,
287
+ UserTopicVisibilityPolicy policy = UserTopicVisibilityPolicy .none,
288
+ ]) async {
289
+ await store.addStream (stream);
290
+ await store.addSubscription (eg.subscription (stream, isMuted: streamMuted));
291
+ await setVisibility (policy);
292
+ }
293
+
294
+ void checkHasMessageIds (Iterable <int > messageIds) {
295
+ check (model.messages.map ((m) => m.id)).deepEquals (messageIds);
296
+ }
297
+
298
+ test ('mute a visible topic' , () async {
299
+ await prepare (narrow: const CombinedFeedNarrow ());
300
+ await prepareMutes ();
301
+ final otherStream = eg.stream ();
302
+ await store.addStream (otherStream);
303
+ await store.addSubscription (eg.subscription (otherStream));
304
+ await prepareMessages (foundOldest: true , messages: [
305
+ eg.streamMessage (id: 1 , stream: stream, topic: 'bar' ),
306
+ eg.streamMessage (id: 2 , stream: stream, topic: 'foo' ),
307
+ eg.streamMessage (id: 3 , stream: otherStream, topic: 'elsewhere' ),
308
+ eg.dmMessage ( id: 4 , from: eg.otherUser, to: [eg.selfUser]),
309
+ ]);
310
+ checkHasMessageIds ([1 , 2 , 3 , 4 ]);
311
+
312
+ await setVisibility (UserTopicVisibilityPolicy .muted);
313
+ checkNotifiedOnce ();
314
+ checkHasMessageIds ([1 , 3 , 4 ]);
315
+ });
316
+
317
+ test ('in CombinedFeedNarrow, use combined-feed visibility' , () async {
318
+ // Compare the parallel StreamNarrow test below.
319
+ await prepare (narrow: const CombinedFeedNarrow ());
320
+ // Mute the stream, so that combined-feed vs. stream visibility differ.
321
+ await prepareMutes (true , UserTopicVisibilityPolicy .followed);
322
+ await prepareMessages (foundOldest: true , messages: [
323
+ eg.streamMessage (id: 1 , stream: stream, topic: 'foo' ),
324
+ ]);
325
+ checkHasMessageIds ([1 ]);
326
+
327
+ // Dropping from followed to none hides the message
328
+ // (whereas it'd have no effect in a stream narrow).
329
+ await setVisibility (UserTopicVisibilityPolicy .none);
330
+ checkNotifiedOnce ();
331
+ checkHasMessageIds ([]);
332
+
333
+ // Dropping from none to muted has no further effect
334
+ // (whereas it'd hide the message in a stream narrow).
335
+ await setVisibility (UserTopicVisibilityPolicy .muted);
336
+ checkNotNotified ();
337
+ checkHasMessageIds ([]);
338
+ });
339
+
340
+ test ('in StreamNarrow, use stream visibility' , () async {
341
+ // Compare the parallel CombinedFeedNarrow test above.
342
+ await prepare (narrow: StreamNarrow (stream.streamId));
343
+ // Mute the stream, so that combined-feed vs. stream visibility differ.
344
+ await prepareMutes (true , UserTopicVisibilityPolicy .followed);
345
+ await prepareMessages (foundOldest: true , messages: [
346
+ eg.streamMessage (id: 1 , stream: stream, topic: 'foo' ),
347
+ ]);
348
+ checkHasMessageIds ([1 ]);
349
+
350
+ // Dropping from followed to none has no effect
351
+ // (whereas it'd hide the message in the combined feed).
352
+ await setVisibility (UserTopicVisibilityPolicy .none);
353
+ checkNotNotified ();
354
+ checkHasMessageIds ([1 ]);
355
+
356
+ // Dropping from none to muted hides the message
357
+ // (whereas it'd have no effect in a stream narrow).
358
+ await setVisibility (UserTopicVisibilityPolicy .muted);
359
+ checkNotifiedOnce ();
360
+ checkHasMessageIds ([]);
361
+ });
362
+
363
+ test ('in TopicNarrow, stay visible' , () async {
364
+ await prepare (narrow: TopicNarrow (stream.streamId, 'foo' ));
365
+ await prepareMutes ();
366
+ await prepareMessages (foundOldest: true , messages: [
367
+ eg.streamMessage (id: 1 , stream: stream, topic: 'foo' ),
368
+ ]);
369
+ checkHasMessageIds ([1 ]);
370
+
371
+ await setVisibility (UserTopicVisibilityPolicy .muted);
372
+ checkNotNotified ();
373
+ checkHasMessageIds ([1 ]);
374
+ });
375
+
376
+ test ('in DmNarrow, do nothing (smoke test)' , () async {
377
+ await prepare (narrow:
378
+ DmNarrow .withUser (eg.otherUser.userId, selfUserId: eg.selfUser.userId));
379
+ await prepareMutes ();
380
+ await prepareMessages (foundOldest: true , messages: [
381
+ eg.dmMessage (id: 1 , from: eg.otherUser, to: [eg.selfUser]),
382
+ ]);
383
+ checkHasMessageIds ([1 ]);
384
+
385
+ await setVisibility (UserTopicVisibilityPolicy .muted);
386
+ checkNotNotified ();
387
+ checkHasMessageIds ([1 ]);
388
+ });
389
+
390
+ test ('no affected messages -> no notification' , () async {
391
+ await prepare (narrow: const CombinedFeedNarrow ());
392
+ await prepareMutes ();
393
+ await prepareMessages (foundOldest: true , messages: [
394
+ eg.streamMessage (id: 1 , stream: stream, topic: 'bar' ),
395
+ ]);
396
+ checkHasMessageIds ([1 ]);
397
+
398
+ await setVisibility (UserTopicVisibilityPolicy .muted);
399
+ checkNotNotified ();
400
+ checkHasMessageIds ([1 ]);
401
+ });
402
+
403
+ test ('unmute a topic -> refetch from scratch' , () => awaitFakeAsync ((async ) async {
404
+ await prepare (narrow: const CombinedFeedNarrow ());
405
+ await prepareMutes (true );
406
+ final messages = [
407
+ eg.dmMessage (id: 1 , from: eg.otherUser, to: [eg.selfUser]),
408
+ eg.streamMessage (id: 2 , stream: stream, topic: 'foo' ),
409
+ ];
410
+ await prepareMessages (foundOldest: true , messages: messages);
411
+ checkHasMessageIds ([1 ]);
412
+
413
+ connection.prepare (
414
+ json: newestResult (foundOldest: true , messages: messages).toJson ());
415
+ await setVisibility (UserTopicVisibilityPolicy .unmuted);
416
+ checkNotifiedOnce ();
417
+ check (model).fetched.isFalse ();
418
+ checkHasMessageIds ([]);
419
+
420
+ async .elapse (Duration .zero);
421
+ checkNotifiedOnce ();
422
+ checkHasMessageIds ([1 , 2 ]);
423
+ }));
424
+
425
+ test ('unmute a topic before initial fetch completes -> do nothing' , () => awaitFakeAsync ((async ) async {
426
+ await prepare (narrow: const CombinedFeedNarrow ());
427
+ await prepareMutes (true );
428
+ final messages = [
429
+ eg.streamMessage (id: 1 , stream: stream, topic: 'foo' ),
430
+ ];
431
+
432
+ connection.prepare (
433
+ json: newestResult (foundOldest: true , messages: messages).toJson ());
434
+ final fetchFuture = model.fetchInitial ();
435
+
436
+ await setVisibility (UserTopicVisibilityPolicy .unmuted);
437
+ checkNotNotified ();
438
+
439
+ // The new policy does get applied when the fetch eventually completes.
440
+ await fetchFuture;
441
+ checkNotifiedOnce ();
442
+ checkHasMessageIds ([1 ]);
443
+ }));
444
+ });
445
+
273
446
group ('DeleteMessageEvent' , () {
274
447
final stream = eg.stream ();
275
448
final messages = List .generate (30 , (i) => eg.streamMessage (stream: stream));
0 commit comments