@@ -10,13 +10,22 @@ import 'package:video_player_platform_interface/video_player_platform_interface.
1010import 'package:video_player/video_player.dart' ;
1111import 'package:zulip/api/model/model.dart' ;
1212import 'package:zulip/model/localizations.dart' ;
13+ import 'package:zulip/model/narrow.dart' ;
14+ import 'package:zulip/model/store.dart' ;
1315import 'package:zulip/widgets/app.dart' ;
1416import 'package:zulip/widgets/content.dart' ;
1517import 'package:zulip/widgets/lightbox.dart' ;
18+ import 'package:zulip/widgets/message_list.dart' ;
19+ import 'package:zulip/widgets/page.dart' ;
20+ import 'package:zulip/widgets/store.dart' ;
1621
22+ import '../api/fake_api.dart' ;
1723import '../example_data.dart' as eg;
1824import '../model/binding.dart' ;
25+ import '../model/content_test.dart' ;
26+ import '../model/test_store.dart' ;
1927import '../test_images.dart' ;
28+ import '../test_navigation.dart' ;
2029import 'dialog_checks.dart' ;
2130import 'test_app.dart' ;
2231
@@ -197,6 +206,39 @@ class FakeVideoPlayerPlatform extends Fake
197206void main () {
198207 TestZulipBinding .ensureInitialized ();
199208
209+ late PerAccountStore store;
210+ late FakeApiConnection connection;
211+
212+ Future <void > setupMessageListPage (WidgetTester tester, {
213+ Narrow narrow = const CombinedFeedNarrow (),
214+ List <Message >? messages,
215+ List <ZulipStream >? streams,
216+ List <NavigatorObserver > navObservers = const [],
217+
218+ }) async {
219+ addTearDown (testBinding.reset);
220+ final stream = streams? .first ?? eg.stream (streamId: eg.defaultStreamMessageStreamId);
221+ final subscription = eg.subscription (stream);
222+ await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot (
223+ streams: streams ?? [stream], subscriptions: [subscription]));
224+ store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
225+ connection = store.connection as FakeApiConnection ;
226+
227+ await store.addUser (eg.selfUser);
228+
229+ prepareBoringImageHttpClient ();
230+
231+ connection.prepare (json:
232+ eg.newestGetMessagesResult (foundOldest: true , messages: messages ?? []).toJson ());
233+
234+ await tester.pumpWidget (TestZulipApp (accountId: eg.selfAccount.id,
235+ navigatorObservers: navObservers,
236+ child: MessageListPage (initNarrow: narrow)));
237+
238+ await tester.pumpAndSettle ();
239+ debugNetworkImageHttpClientProvider = null ;
240+ }
241+
200242 group ('_ImageLightboxPage' , () {
201243 final src = Uri .parse ('https://chat.example/lightbox-image.png' );
202244
@@ -207,14 +249,19 @@ void main() {
207249 addTearDown (testBinding.reset);
208250 await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
209251
210- // ZulipApp instead of TestZulipApp because we need the navigator to push
211- // the lightbox route. The lightbox page works together with the route;
212- // it takes the route's entrance animation.
213- await tester.pumpWidget (const ZulipApp ());
252+ // ZulipApp instead of TestZulipApp because we need:
253+ // 1. The navigator to push the lightbox route. The lightbox page works
254+ // together with the route; it takes the route's entrance animation.
255+ // 2. The PageRoot widget to provide context for Hero animations between
256+ // the message list and lightbox.
257+ await tester.pumpWidget (PageRoot (
258+ child: const ZulipApp ()
259+ ));
214260 await tester.pump ();
215261 final navigator = await ZulipApp .navigator;
216262 unawaited (navigator.push (getImageLightboxRoute (
217263 accountId: eg.selfAccount.id,
264+ pageContext: PageRoot .contextOf (navigator.context),
218265 message: message ?? eg.streamMessage (),
219266 src: src,
220267 thumbnailUrl: thumbnailUrl,
@@ -558,4 +605,95 @@ void main() {
558605 check (platform.position).equals (position);
559606 });
560607 });
561- }
608+
609+ group ('LightboxHero' , () {
610+ testWidgets ('no hero animation occurs between different message list pages for same image' , (tester) async {
611+ final pushedRoutes = < Route <dynamic >> [];
612+ final testNavObserver = TestNavigatorObserver ()
613+ ..onPushed = (route, prevRoute) => pushedRoutes.add (route);
614+ final channel = eg.stream (streamId: eg.defaultStreamMessageStreamId);
615+ final message = eg.streamMessage (stream: channel,
616+ contentMarkdown: ContentExample .imageSingle.html, topic: 'test topic' );
617+ await setupMessageListPage (tester,
618+ narrow: const CombinedFeedNarrow (),
619+ streams: [channel],
620+ messages: [message],
621+ navObservers: [testNavObserver]);
622+
623+ assert (pushedRoutes.length == 1 );
624+ pushedRoutes.clear ();
625+
626+ final heroTag1 = tester.widget <Hero >(
627+ find.descendant (
628+ of: find.byType (LightboxHero ),
629+ matching: find.byType (Hero ),
630+ )
631+ );
632+
633+ connection.prepare (json:
634+ eg.newestGetMessagesResult (foundOldest: true , messages: [message]).toJson ());
635+
636+ await tester.tap (find.descendant (
637+ of: find.byType (StreamMessageRecipientHeader ),
638+ matching: find.text ('test topic' )));
639+ await tester.pump ();
640+
641+ final heroFinder = find.descendant (
642+ of: find.byType (Overlay ),
643+ matching: find.byType (Hero ),
644+ );
645+ expect (heroFinder, findsOneWidget);
646+ await tester.pumpAndSettle ();
647+
648+ final heroTag2 = tester.widget <Hero >(
649+ find.descendant (
650+ of: find.byType (LightboxHero ),
651+ matching: find.byType (Hero ),
652+ )
653+ );
654+ expect (heroTag1.tag, isNot (equals (heroTag2.tag)));
655+ });
656+
657+ testWidgets ('hero animation occurs when opening lightbox from message list' , (tester) async {
658+ final pushedRoutes = < Route <dynamic >> [];
659+ final testNavObserver = TestNavigatorObserver ()
660+ ..onPushed = (route, prevRoute) => pushedRoutes.add (route);
661+ final channel = eg.stream (streamId: eg.defaultStreamMessageStreamId);
662+ final message = eg.streamMessage (stream: channel,
663+ contentMarkdown: ContentExample .spoilerHeaderHasImage.html);
664+ await setupMessageListPage (tester,
665+ narrow: const CombinedFeedNarrow (),
666+ streams: [channel],
667+ messages: [message],
668+ navObservers: [testNavObserver]);
669+
670+ connection.prepare (json:
671+ eg.newestGetMessagesResult (foundOldest: true , messages: [message]).toJson ());
672+
673+ pushedRoutes.clear ();
674+
675+ final initialHero = tester.widget <Hero >(
676+ find.descendant (
677+ of: find.byType (LightboxHero ),
678+ matching: find.byType (Hero ),
679+ )
680+ );
681+ expect (initialHero.flightShuttleBuilder, isNotNull);
682+ await tester.tap (find.byType (RealmContentNetworkImage ));
683+ await tester.pump ();
684+ await tester.pump (const Duration (milliseconds: 150 ));
685+ expect (find.byType (PerAccountStoreWidget ), findsWidgets);
686+ await tester.pumpAndSettle ();
687+
688+ final lightboxHero = tester.widget <Hero >(
689+ find.descendant (
690+ of: find.byType (Overlay ),
691+ matching: find.byType (Hero ),
692+ )
693+ );
694+ check (lightboxHero.tag).equals (initialHero.tag);
695+ check (lightboxHero.flightShuttleBuilder).isNotNull ();
696+ debugNetworkImageHttpClientProvider = null ;
697+ });
698+ });
699+ }
0 commit comments