@@ -470,212 +470,146 @@ void main() {
470470      }
471471    });
472472
473-     // For the reader: 
474-     // This implements tests without heavily parameterizing them; 
475-     // see and compare with the next commit that changes this. 
476473    group ('moves' , () {
477-       final  origChannel =  eg.stream ();
474+       final  origChannel =  eg.stream (name:  'old channel' );
475+       final  newChannel =  eg.stream (name:  'new channel' );
476+       final  newUnsubscribedChannel =  eg.stream (name:  'new unsubscribed channel' );
478477      const  origTopic =  'origTopic' ;
479478      const  newTopic =  'newTopic' ;
480479
481-       Future <void > prepareStore () async  {
482-         prepare ();
483-         await  channelStore.addStream (origChannel);
484-         await  channelStore.addSubscription (eg.subscription (origChannel));
485-       }
486- 
487-       group ('move read messages' , () {
488-         final  readMessages =  List <StreamMessage >.generate (10 ,
489-           (_) =>  eg.streamMessage (
490-             stream:  origChannel, topic:  origTopic, flags:  [MessageFlag .read]));
491- 
492-         test ('to new channel' , () async  {
493-           await  prepareStore ();
494-           final  newChannel =  eg.stream ();
495-           await  channelStore.addStream (newChannel);
496-           await  channelStore.addSubscription (eg.subscription (newChannel));
497-           fillWithMessages (readMessages);
498- 
499-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
500-             origMessages:  readMessages,
501-             newStreamId:  newChannel.streamId));
502-           checkNotNotified ();
503-           checkMatchesMessages ([]);
504-         });
505- 
506-         test ('to new topic' , () async  {
507-           await  prepareStore ();
508-           fillWithMessages (readMessages);
480+       void  doTestMove (String  moveDescription, {
481+         ZulipStream ?  channelAfterMove,
482+         String ?  topicAfterMove,
483+         required  Set <StreamMessage > messages,
484+         required  Set <StreamMessage > messagesToMove,
485+         required  Set <StreamMessage > expectedUnreadMessagesAfterMove,
486+       }) async  {
487+         assert (messages.containsAll (messagesToMove));
488+         assert (channelAfterMove !=  null  ||  topicAfterMove !=  null );
489+         channelAfterMove ?? =  origChannel;
490+         topicAfterMove ?? =  origTopic;
491+ 
492+         test ('$moveDescription  from #${origChannel .name } > $origTopic , to #${channelAfterMove .name } > $newTopic ' , () async  {
493+           prepare ();
494+           await  channelStore.addStreams (
495+             [origChannel, newChannel, newUnsubscribedChannel]);
496+           await  channelStore.addSubscriptions (
497+             [eg.subscription (origChannel), eg.subscription (newChannel)]);
498+           fillWithMessages (messages);
509499
510500          model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
511-             origMessages:  readMessages,
512-             newTopicStr:  newTopic));
513-           checkNotNotified ();
514-           checkMatchesMessages ([]);
515-         });
516- 
517-         test ('from topic with unreads' , () async  {
518-           await  prepareStore ();
519-           final  unreadMessage =  eg.streamMessage (
520-             stream:  origChannel, topic:  origTopic);
521-           fillWithMessages ([...readMessages, unreadMessage]);
522- 
523-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
524-             origMessages:  readMessages,
525-             newTopicStr:  newTopic));
526-           checkNotNotified ();
527-           checkMatchesMessages ([unreadMessage]);
528-         });
529- 
530-         test ('to topic with unreads' , () async  {
531-           await  prepareStore ();
532-           final  unreadMessage =  eg.streamMessage (
533-             stream:  origChannel, topic:  newTopic);
534-           fillWithMessages ([...readMessages, unreadMessage]);
535- 
536-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
537-             origMessages:  readMessages,
538-             newTopicStr:  newTopic,
501+             origMessages:  messagesToMove.toList (),
502+             newStreamId:  channelAfterMove! .streamId,
503+             newTopicStr:  topicAfterMove,
539504          ));
540-           checkNotNotified ();
541-           checkMatchesMessages ([unreadMessage]);
542-         });
543-       });
544- 
545-       group ('move unread messages' , () {
546-         final  unreadMessages =  List <StreamMessage >.generate (10 ,
547-           (_) =>  eg.streamMessage (stream:  origChannel, topic:  origTopic));
548- 
549-         test ('to another subscribed channel; same topic name' , () async  {
550-           await  prepareStore ();
551-           final  newChannel =  eg.stream ();
552-           await  channelStore.addStream (newChannel);
553-           await  channelStore.addSubscription (eg.subscription (newChannel));
554-           fillWithMessages (unreadMessages);
555- 
556-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
557-             origMessages:  unreadMessages,
558-             newStreamId:  newChannel.streamId));
559-           checkNotifiedOnce ();
560-           checkMatchesMessages ([
561-             for  (final  message in  unreadMessages)
562-               Message .fromJson (
563-                 message.toJson ()..['stream_id' ] =  newChannel.streamId),
564-           ]);
565-         });
566- 
567-         test ('to another subscribed channel; different topic name' , () async  {
568-           await  prepareStore ();
569-           final  newChannel =  eg.stream ();
570-           await  channelStore.addStream (newChannel);
571-           await  channelStore.addSubscription (eg.subscription (newChannel));
572-           fillWithMessages (unreadMessages);
573- 
574-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
575-             origMessages:  unreadMessages,
576-             newStreamId:  newChannel.streamId,
577-             newTopicStr:  newTopic));
578-           checkNotifiedOnce ();
579-           checkMatchesMessages ([
580-             for  (final  message in  unreadMessages)
581-               Message .fromJson (
582-                 message.toJson ()
583-                   ..['stream_id' ] =  newChannel.streamId
584-                   ..['subject' ] =  newTopic
585-               ),
586-           ]);
587-         });
588- 
589-         test ('to unsubscribed channel' , () async  {
590-           await  prepareStore ();
591-           final  newChannel =  eg.stream ();
592-           await  channelStore.addStream (newChannel);
593-           assert (! channelStore.subscriptions.containsKey (newChannel.streamId));
594-           fillWithMessages (unreadMessages);
595- 
596-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
597-             origMessages:  unreadMessages,
598-             newStreamId:  newChannel.streamId));
599-           checkNotifiedOnce ();
600-           checkMatchesMessages ([]);
601-         });
602- 
603-         test ('to new topic' , () async  {
604-           await  prepareStore ();
605-           fillWithMessages (unreadMessages);
606- 
607-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
608-             origMessages:  unreadMessages,
609-             newTopicStr:  newTopic));
610-           checkNotifiedOnce ();
611-           checkMatchesMessages ([
612-             for  (final  message in  unreadMessages)
613-               Message .fromJson (message.toJson ()..['subject' ] =  newTopic),
614-           ]);
615-         });
616- 
617-         test ('from topic containing other unreads' , () async  {
618-           await  prepareStore ();
619-           final  unreadMessage =  eg.streamMessage (
620-             stream:  origChannel, topic:  origTopic);
621-           fillWithMessages ([...unreadMessages, unreadMessage]);
622- 
623-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
624-             origMessages:  unreadMessages,
625-             newTopicStr:  newTopic));
626-           checkNotifiedOnce ();
505+           final  movedUnreads = 
506+             expectedUnreadMessagesAfterMove.intersection (messagesToMove);
507+           final  otherUnreads = 
508+             expectedUnreadMessagesAfterMove.difference (messagesToMove);
627509          checkMatchesMessages ([
628-             for  (final  message in  unreadMessages)
629-               Message .fromJson (message.toJson ()..['subject' ] =  newTopic),
630-             unreadMessage,
510+             for  (final  message in  movedUnreads)
511+               StreamMessage .fromJson (message.toJson ()
512+                 ..['stream_id' ] =  channelAfterMove.streamId
513+                 ..['subject' ] =  topicAfterMove),
514+             ...otherUnreads,
631515          ]);
516+           // The union of `movedUnreads` and `otherUnreads` might not contain 
517+           // some unreads found with this check, since unreads can be dropped 
518+           // when they are moved to an unsubscribed channel. 
519+           messagesToMove.any (
520+             (message) =>  ! message.flags.contains (MessageFlag .read)
521+           ) ?  checkNotifiedOnce ()
522+             :  checkNotNotified ();
632523        });
524+       }
633525
634-         test ('to topic containing other unreads' , () async  {
635-           await  prepareStore ();
636-           final  unreadMessage =  eg.streamMessage (
637-             stream:  origChannel, topic:  newTopic);
638-           fillWithMessages ([...unreadMessages, unreadMessage]);
526+       final  unreadMessages =  List <StreamMessage >.generate (10 ,
527+         (_) =>  eg.streamMessage (stream:  origChannel, topic:  origTopic)).toSet ();
528+       final  readMessages =  List <StreamMessage >.generate (10 ,
529+         (_) =>  eg.streamMessage (
530+           stream:  origChannel, topic:  origTopic, flags:  [MessageFlag .read])).toSet ();
531+       final  messagesAtStart =  unreadMessages.union (readMessages);
532+       final  messagesToMoveCases =  [
533+         ('move some unread messages' , unreadMessages.take (5 ).toSet ()),
534+         ('move some read messages' ,   readMessages.take (5 ).toSet ()),
535+         ('move all unread messages' ,  unreadMessages),
536+         ('move all read messages' ,    readMessages),
537+         ('move all messages' ,         messagesAtStart),
538+       ];
639539
640-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
641-             origMessages:  unreadMessages,
642-             newTopicStr:  newTopic));
643-           checkNotifiedOnce ();
644-           checkMatchesMessages ([
645-             for  (final  message in  unreadMessages)
646-               Message .fromJson (message.toJson ()..['subject' ] =  newTopic),
647-             unreadMessage,
648-           ]);
649-         });
540+       group ('move to conversations with unreads' , () {
541+         final  destinations =  [
542+           (newChannel, newTopic),
543+           (newChannel, null ),
544+           (null ,       newTopic),
545+         ];
546+         for  (final  (moveDescription, messagesToMove) in  messagesToMoveCases) {
547+           for  (final  (channel, topic) in  destinations) {
548+             final  unreadMessagesAtDestination =  List <StreamMessage >.generate (10 ,
549+               (_) =>  eg.streamMessage (stream:  channel, topic:  topic)).toSet ();
550+             doTestMove (moveDescription,
551+               channelAfterMove:  channel,
552+               topicAfterMove:  topic,
553+               messages:  messagesAtStart.union (unreadMessagesAtDestination),
554+               messagesToMove:  messagesToMove,
555+               expectedUnreadMessagesAfterMove: 
556+                 unreadMessages.union (unreadMessagesAtDestination));
557+           }
558+         }
559+       });
650560
651-         test ('tolerates unsorted messages' , () async  {
652-           await  prepareStore ();
653-           final  unreadMessages =  List .generate (10 ,
654-             (i) =>  eg.streamMessage (id:  1000 - i, stream:  origChannel, topic:  origTopic));
655-           fillWithMessages (unreadMessages);
561+       group ('move to conversations in unsubscribed channels' , () {
562+         final  unsubscribedDestinations =  [
563+           (newUnsubscribedChannel, newTopic),
564+           (newUnsubscribedChannel, null ),
565+         ];
566+         for  (final  (moveDescription, messagesToMove) in  messagesToMoveCases) {
567+           for  (final  (channel, topic) in  unsubscribedDestinations) {
568+             doTestMove (moveDescription,
569+               channelAfterMove:  channel,
570+               topicAfterMove:  topic,
571+               messages:  messagesAtStart,
572+               messagesToMove:  messagesToMove,
573+               expectedUnreadMessagesAfterMove: 
574+                 unreadMessages.difference (messagesToMove));
575+           }
576+         }
577+       });
656578
657-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
658-             origMessages:  unreadMessages,
659-             newTopicStr:  newTopic));
660-           checkNotifiedOnce ();
661-           checkMatchesMessages ([
662-             for  (final  message in  unreadMessages)
663-               Message .fromJson (message.toJson ()..['subject' ] =  newTopic)
664-           ]);
665-         });
579+       test ('tolerates unsorted messages' , () async  {
580+         prepare ();
581+         await  channelStore.addStream (origChannel);
582+         await  channelStore.addSubscription (eg.subscription (origChannel));
583+         final  unreadMessages =  List .generate (10 ,
584+           (i) =>  eg.streamMessage (id:  1000 - i, stream:  origChannel, topic:  origTopic));
585+         fillWithMessages (unreadMessages);
666586
667-         test ('tolerates unreads unknown to the model' , () async  {
668-           await  prepareStore ();
669-           final  unknownUnreadMessage =  eg.streamMessage (
670-             stream:  eg.stream (), topic:  origTopic);
671-           fillWithMessages (unreadMessages);
587+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
588+           origMessages:  unreadMessages,
589+           newTopicStr:  newTopic));
590+         checkNotifiedOnce ();
591+         checkMatchesMessages ([
592+           for  (final  message in  unreadMessages)
593+             Message .fromJson (message.toJson ()..['subject' ] =  newTopic)
594+         ]);
595+       });
672596
673-           model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
674-             origMessages:  [unknownUnreadMessage],
675-             newTopicStr:  newTopic));
676-           checkNotNotified ();
677-           checkMatchesMessages (unreadMessages);
678-         });
597+       test ('tolerates unreads unknown to the model' , () async  {
598+         prepare ();
599+         await  channelStore.addStream (origChannel);
600+         await  channelStore.addSubscription (eg.subscription (origChannel));
601+         fillWithMessages (unreadMessages);
602+ 
603+         final  unknownChannel =  eg.stream ();
604+         assert (! channelStore.streams.containsKey (unknownChannel.streamId));
605+         final  unknownUnreadMessage =  eg.streamMessage (
606+           stream:  unknownChannel, topic:  origTopic);
607+ 
608+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
609+           origMessages:  [unknownUnreadMessage],
610+           newTopicStr:  newTopic));
611+         checkNotNotified ();
612+         checkMatchesMessages (unreadMessages);
679613      });
680614    });
681615  });
0 commit comments