@@ -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