@@ -469,6 +469,175 @@ void main() {
469469        }
470470      }
471471    });
472+ 
473+     group ('moves' , () {
474+       final  origChannel =  eg.stream ();
475+       const  origTopic =  'origTopic' ;
476+       const  newTopic =  'newTopic' ;
477+ 
478+       late  List <StreamMessage > readMessages;
479+       late  List <StreamMessage > unreadMessages;
480+ 
481+       Future <void > prepareStore () async  {
482+         prepare ();
483+         await  channelStore.addStream (origChannel);
484+         await  channelStore.addSubscription (eg.subscription (origChannel));
485+         readMessages  =  List <StreamMessage >.generate (10 ,
486+           (_) =>  eg.streamMessage (stream:  origChannel, topic:  origTopic,
487+                    flags:  [MessageFlag .read]));
488+         unreadMessages =  List <StreamMessage >.generate (10 ,
489+           (_) =>  eg.streamMessage (stream:  origChannel, topic:  origTopic));
490+       }
491+ 
492+       List <StreamMessage > copyMessagesWith (Iterable <StreamMessage > messages, {
493+         ZulipStream ?  newChannel,
494+         String ?  newTopic,
495+       }) {
496+         assert (newChannel !=  null  ||  newTopic !=  null );
497+         return  messages.map ((message) =>  StreamMessage .fromJson (
498+           message.toJson ()
499+             ..['stream_id' ] =  newChannel? .streamId ??  message.streamId
500+             ..['subject' ] =  newTopic ??  message.topic
501+         )).toList ();
502+       }
503+ 
504+       test ('moved messages = unread messages' , () async  {
505+         await  prepareStore ();
506+         final  newChannel =  eg.stream ();
507+         await  channelStore.addStream (newChannel);
508+         await  channelStore.addSubscription (eg.subscription (newChannel));
509+         fillWithMessages (unreadMessages);
510+         final  originalMessageIds = 
511+           model.streams[origChannel.streamId]! [TopicName (origTopic)]! ;
512+ 
513+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
514+           origMessages:  unreadMessages,
515+           newStreamId:  newChannel.streamId,
516+           newTopicStr:  newTopic));
517+         checkNotifiedOnce ();
518+         checkMatchesMessages (copyMessagesWith (unreadMessages,
519+           newChannel:  newChannel, newTopic:  newTopic));
520+         final  newMessageIds = 
521+           model.streams[newChannel.streamId]! [TopicName (newTopic)]! ;
522+         // Check we successfully avoided making a copy of the list. 
523+         check (originalMessageIds).identicalTo (newMessageIds);
524+       });
525+ 
526+       test ('moved messages ⊂ read messages' , () async  {
527+         await  prepareStore ();
528+         final  messagesToMove =  readMessages.take (2 ).toList ();
529+         fillWithMessages (unreadMessages +  readMessages);
530+ 
531+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
532+           origMessages:  messagesToMove,
533+           newTopicStr:  newTopic));
534+         checkNotNotified ();
535+         checkMatchesMessages (unreadMessages);
536+       });
537+ 
538+       test ('moved messages ⊂ unread messages' , () async  {
539+         await  prepareStore ();
540+         final  messagesToMove =  unreadMessages.take (2 ).toList ();
541+         fillWithMessages (unreadMessages +  readMessages);
542+ 
543+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
544+           origMessages:  messagesToMove,
545+           newTopicStr:  newTopic));
546+         checkNotifiedOnce ();
547+         checkMatchesMessages ([
548+           ...copyMessagesWith (messagesToMove, newTopic:  newTopic),
549+           ...unreadMessages.skip (2 ),
550+         ]);
551+       });
552+ 
553+       test ('moved messages ∩ unread messages ≠ Ø, moved messages ∩ read messages ≠ Ø, moved messages ⊅ unread messages' , () async  {
554+         await  prepareStore ();
555+         final  messagesToMove =  [unreadMessages.first, readMessages.first];
556+         fillWithMessages (unreadMessages +  readMessages);
557+ 
558+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
559+           origMessages:  messagesToMove,
560+           newTopicStr:  newTopic));
561+         checkNotifiedOnce ();
562+         checkMatchesMessages ([
563+           ...copyMessagesWith (unreadMessages.take (1 ), newTopic:  newTopic),
564+           ...unreadMessages.skip (1 ),
565+         ]);
566+       });
567+ 
568+       test ('moved messages ⊃ unread messages' , () async  {
569+         await  prepareStore ();
570+         final  messagesToMove =  unreadMessages +  readMessages.take (2 ).toList ();
571+         fillWithMessages (unreadMessages +  readMessages);
572+         final  originalMessageIds = 
573+           model.streams[origChannel.streamId]! [TopicName (origTopic)]! ;
574+ 
575+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
576+           origMessages:  messagesToMove,
577+           newTopicStr:  newTopic));
578+         checkNotifiedOnce ();
579+         checkMatchesMessages (copyMessagesWith (unreadMessages, newTopic:  newTopic));
580+         final  newMessageIds = 
581+           model.streams[origChannel.streamId]! [TopicName (newTopic)]! ;
582+         // Check we successfully avoided making a copy of the list. 
583+         check (originalMessageIds).identicalTo (newMessageIds);
584+       });
585+ 
586+       test ('moving to unsubscribed channels drops the unreads' , () async  {
587+         await  prepareStore ();
588+         final  unsubscribedChannel =  eg.stream ();
589+         await  channelStore.addStream (unsubscribedChannel);
590+         assert (! channelStore.subscriptions.containsKey (
591+           unsubscribedChannel.streamId));
592+         fillWithMessages (unreadMessages);
593+ 
594+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
595+           origMessages:  unreadMessages,
596+           newStreamId:  unsubscribedChannel.streamId));
597+         checkNotifiedOnce ();
598+         checkMatchesMessages ([]);
599+       });
600+ 
601+       test ('tolerates unsorted messages' , () async  {
602+         await  prepareStore ();
603+         final  unreadMessages =  List .generate (10 , (i) => 
604+           eg.streamMessage (
605+             id:  1000  -  i, stream:  origChannel, topic:  origTopic));
606+         fillWithMessages (unreadMessages);
607+ 
608+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
609+           origMessages:  unreadMessages,
610+           newTopicStr:  newTopic));
611+         checkNotifiedOnce ();
612+         checkMatchesMessages (copyMessagesWith (unreadMessages, newTopic:  newTopic));
613+       });
614+ 
615+       test ('tolerates unreads unknown to the model' , () async  {
616+         await  prepareStore ();
617+         fillWithMessages (unreadMessages);
618+ 
619+         final  unknownChannel =  eg.stream ();
620+         assert (! channelStore.streams.containsKey (unknownChannel.streamId));
621+         final  unknownUnreadMessage =  eg.streamMessage (
622+           stream:  unknownChannel, topic:  origTopic);
623+ 
624+         model.handleUpdateMessageEvent (eg.updateMessageEventMoveFrom (
625+           origMessages:  [unknownUnreadMessage],
626+           newTopicStr:  newTopic));
627+         checkNotNotified ();
628+         checkMatchesMessages (unreadMessages);
629+       });
630+ 
631+       test ('message edit but no move' , () async  {
632+         await  prepareStore ();
633+         fillWithMessages (unreadMessages);
634+ 
635+         model.handleUpdateMessageEvent (eg.updateMessageEditEvent (
636+           unreadMessages.first));
637+         checkNotNotified ();
638+         checkMatchesMessages (unreadMessages);
639+       });
640+     });
472641  });
473642
474643
0 commit comments