@@ -311,6 +311,179 @@ void main() {
311
311
check (model).fetched.isFalse ();
312
312
});
313
313
314
+ group ('UserTopicEvent' , () {
315
+ // The ChannelStore.willChangeIfTopicVisible/InStream methods have their own
316
+ // thorough unit tests. So these tests focus on the rest of the logic.
317
+
318
+ final stream = eg.stream ();
319
+ const String topic = 'foo' ;
320
+
321
+ Future <void > setVisibility (UserTopicVisibilityPolicy policy) async {
322
+ await store.handleEvent (eg.userTopicEvent (stream.streamId, topic, policy));
323
+ }
324
+
325
+ /// (Should run after `prepare` .)
326
+ Future <void > prepareMutes ([
327
+ bool streamMuted = false ,
328
+ UserTopicVisibilityPolicy policy = UserTopicVisibilityPolicy .none,
329
+ ]) async {
330
+ await store.addStream (stream);
331
+ await store.addSubscription (eg.subscription (stream, isMuted: streamMuted));
332
+ await setVisibility (policy);
333
+ }
334
+
335
+ void checkHasMessageIds (Iterable <int > messageIds) {
336
+ check (model.messages.map ((m) => m.id)).deepEquals (messageIds);
337
+ }
338
+
339
+ test ('mute a visible topic' , () async {
340
+ await prepare (narrow: const CombinedFeedNarrow ());
341
+ await prepareMutes ();
342
+ final otherStream = eg.stream ();
343
+ await store.addStream (otherStream);
344
+ await store.addSubscription (eg.subscription (otherStream));
345
+ await prepareMessages (foundOldest: true , messages: [
346
+ eg.streamMessage (id: 1 , stream: stream, topic: 'bar' ),
347
+ eg.streamMessage (id: 2 , stream: stream, topic: topic),
348
+ eg.streamMessage (id: 3 , stream: otherStream, topic: 'elsewhere' ),
349
+ eg.dmMessage ( id: 4 , from: eg.otherUser, to: [eg.selfUser]),
350
+ ]);
351
+ checkHasMessageIds ([1 , 2 , 3 , 4 ]);
352
+
353
+ await setVisibility (UserTopicVisibilityPolicy .muted);
354
+ checkNotifiedOnce ();
355
+ checkHasMessageIds ([1 , 3 , 4 ]);
356
+ });
357
+
358
+ test ('in CombinedFeedNarrow, use combined-feed visibility' , () async {
359
+ // Compare the parallel ChannelNarrow test below.
360
+ await prepare (narrow: const CombinedFeedNarrow ());
361
+ // Mute the stream, so that combined-feed vs. stream visibility differ.
362
+ await prepareMutes (true , UserTopicVisibilityPolicy .followed);
363
+ await prepareMessages (foundOldest: true , messages: [
364
+ eg.streamMessage (id: 1 , stream: stream, topic: topic),
365
+ ]);
366
+ checkHasMessageIds ([1 ]);
367
+
368
+ // Dropping from followed to none hides the message
369
+ // (whereas it'd have no effect in a stream narrow).
370
+ await setVisibility (UserTopicVisibilityPolicy .none);
371
+ checkNotifiedOnce ();
372
+ checkHasMessageIds ([]);
373
+
374
+ // Dropping from none to muted has no further effect
375
+ // (whereas it'd hide the message in a stream narrow).
376
+ await setVisibility (UserTopicVisibilityPolicy .muted);
377
+ checkNotNotified ();
378
+ checkHasMessageIds ([]);
379
+ });
380
+
381
+ test ('in ChannelNarrow, use stream visibility' , () async {
382
+ // Compare the parallel CombinedFeedNarrow test above.
383
+ await prepare (narrow: ChannelNarrow (stream.streamId));
384
+ // Mute the stream, so that combined-feed vs. stream visibility differ.
385
+ await prepareMutes (true , UserTopicVisibilityPolicy .followed);
386
+ await prepareMessages (foundOldest: true , messages: [
387
+ eg.streamMessage (id: 1 , stream: stream, topic: topic),
388
+ ]);
389
+ checkHasMessageIds ([1 ]);
390
+
391
+ // Dropping from followed to none has no effect
392
+ // (whereas it'd hide the message in the combined feed).
393
+ await setVisibility (UserTopicVisibilityPolicy .none);
394
+ checkNotNotified ();
395
+ checkHasMessageIds ([1 ]);
396
+
397
+ // Dropping from none to muted hides the message
398
+ // (whereas it'd have no effect in a stream narrow).
399
+ await setVisibility (UserTopicVisibilityPolicy .muted);
400
+ checkNotifiedOnce ();
401
+ checkHasMessageIds ([]);
402
+ });
403
+
404
+ test ('in TopicNarrow, stay visible' , () async {
405
+ await prepare (narrow: TopicNarrow (stream.streamId, topic));
406
+ await prepareMutes ();
407
+ await prepareMessages (foundOldest: true , messages: [
408
+ eg.streamMessage (id: 1 , stream: stream, topic: topic),
409
+ ]);
410
+ checkHasMessageIds ([1 ]);
411
+
412
+ await setVisibility (UserTopicVisibilityPolicy .muted);
413
+ checkNotNotified ();
414
+ checkHasMessageIds ([1 ]);
415
+ });
416
+
417
+ test ('in DmNarrow, do nothing (smoke test)' , () async {
418
+ await prepare (narrow:
419
+ DmNarrow .withUser (eg.otherUser.userId, selfUserId: eg.selfUser.userId));
420
+ await prepareMutes ();
421
+ await prepareMessages (foundOldest: true , messages: [
422
+ eg.dmMessage (id: 1 , from: eg.otherUser, to: [eg.selfUser]),
423
+ ]);
424
+ checkHasMessageIds ([1 ]);
425
+
426
+ await setVisibility (UserTopicVisibilityPolicy .muted);
427
+ checkNotNotified ();
428
+ checkHasMessageIds ([1 ]);
429
+ });
430
+
431
+ test ('no affected messages -> no notification' , () async {
432
+ await prepare (narrow: const CombinedFeedNarrow ());
433
+ await prepareMutes ();
434
+ await prepareMessages (foundOldest: true , messages: [
435
+ eg.streamMessage (id: 1 , stream: stream, topic: 'bar' ),
436
+ ]);
437
+ checkHasMessageIds ([1 ]);
438
+
439
+ await setVisibility (UserTopicVisibilityPolicy .muted);
440
+ checkNotNotified ();
441
+ checkHasMessageIds ([1 ]);
442
+ });
443
+
444
+ test ('unmute a topic -> refetch from scratch' , () => awaitFakeAsync ((async ) async {
445
+ await prepare (narrow: const CombinedFeedNarrow ());
446
+ await prepareMutes (true );
447
+ final messages = [
448
+ eg.dmMessage (id: 1 , from: eg.otherUser, to: [eg.selfUser]),
449
+ eg.streamMessage (id: 2 , stream: stream, topic: topic),
450
+ ];
451
+ await prepareMessages (foundOldest: true , messages: messages);
452
+ checkHasMessageIds ([1 ]);
453
+
454
+ connection.prepare (
455
+ json: newestResult (foundOldest: true , messages: messages).toJson ());
456
+ await setVisibility (UserTopicVisibilityPolicy .unmuted);
457
+ checkNotifiedOnce ();
458
+ check (model).fetched.isFalse ();
459
+ checkHasMessageIds ([]);
460
+
461
+ async .elapse (Duration .zero);
462
+ checkNotifiedOnce ();
463
+ checkHasMessageIds ([1 , 2 ]);
464
+ }));
465
+
466
+ test ('unmute a topic before initial fetch completes -> do nothing' , () => awaitFakeAsync ((async ) async {
467
+ await prepare (narrow: const CombinedFeedNarrow ());
468
+ await prepareMutes (true );
469
+ final messages = [
470
+ eg.streamMessage (id: 1 , stream: stream, topic: topic),
471
+ ];
472
+
473
+ connection.prepare (
474
+ json: newestResult (foundOldest: true , messages: messages).toJson ());
475
+ final fetchFuture = model.fetchInitial ();
476
+
477
+ await setVisibility (UserTopicVisibilityPolicy .unmuted);
478
+ checkNotNotified ();
479
+
480
+ // The new policy does get applied when the fetch eventually completes.
481
+ await fetchFuture;
482
+ checkNotifiedOnce ();
483
+ checkHasMessageIds ([1 ]);
484
+ }));
485
+ });
486
+
314
487
group ('DeleteMessageEvent' , () {
315
488
final stream = eg.stream ();
316
489
final messages = List .generate (30 , (i) => eg.streamMessage (stream: stream));
0 commit comments