@@ -10,12 +10,20 @@ import 'package:video_player_platform_interface/video_player_platform_interface.
10
10
import 'package:video_player/video_player.dart' ;
11
11
import 'package:zulip/api/model/model.dart' ;
12
12
import 'package:zulip/model/localizations.dart' ;
13
+ import 'package:zulip/model/narrow.dart' ;
14
+ import 'package:zulip/model/store.dart' ;
13
15
import 'package:zulip/widgets/app.dart' ;
14
16
import 'package:zulip/widgets/content.dart' ;
15
17
import 'package:zulip/widgets/lightbox.dart' ;
18
+ import 'package:zulip/widgets/message_list.dart' ;
19
+ import 'package:zulip/widgets/page.dart' ;
16
20
21
+ import '../api/fake_api.dart' ;
17
22
import '../example_data.dart' as eg;
23
+ import '../flutter_checks.dart' ;
18
24
import '../model/binding.dart' ;
25
+ import '../model/content_test.dart' ;
26
+ import '../model/test_store.dart' ;
19
27
import '../test_images.dart' ;
20
28
import 'dialog_checks.dart' ;
21
29
import 'test_app.dart' ;
@@ -207,14 +215,19 @@ void main() {
207
215
addTearDown (testBinding.reset);
208
216
await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
209
217
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 ());
218
+ // ZulipApp instead of TestZulipApp because we need:
219
+ // 1. The navigator to push the lightbox route. The lightbox page works
220
+ // together with the route; it takes the route's entrance animation.
221
+ // 2. The PageRoot widget to provide context for Hero animations between
222
+ // the message list and lightbox.
223
+ await tester.pumpWidget (PageRoot (
224
+ child: const ZulipApp ()
225
+ ));
214
226
await tester.pump ();
215
227
final navigator = await ZulipApp .navigator;
216
228
unawaited (navigator.push (getImageLightboxRoute (
217
229
accountId: eg.selfAccount.id,
230
+ pageContext: PageRoot .contextOf (navigator.context),
218
231
message: message ?? eg.streamMessage (),
219
232
src: src,
220
233
thumbnailUrl: thumbnailUrl,
@@ -558,4 +571,77 @@ void main() {
558
571
check (platform.position).equals (position);
559
572
});
560
573
});
574
+
575
+ group ('LightboxHero' , () {
576
+ late PerAccountStore store;
577
+ late FakeApiConnection connection;
578
+
579
+ final channel = eg.stream ();
580
+ final message = eg.streamMessage (stream: channel,
581
+ contentMarkdown: ContentExample .imageSingle.html, topic: 'test topic' );
582
+
583
+ Future <void > setupMessageListPage (WidgetTester tester) async {
584
+ addTearDown (testBinding.reset);
585
+ final subscription = eg.subscription (channel);
586
+ await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot (
587
+ streams: [channel], subscriptions: [subscription]));
588
+ store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
589
+ connection = store.connection as FakeApiConnection ;
590
+
591
+ await store.addUser (eg.selfUser);
592
+
593
+ connection.prepare (json:
594
+ eg.newestGetMessagesResult (foundOldest: true , messages: [message]).toJson ());
595
+
596
+ await tester.pumpWidget (TestZulipApp (accountId: eg.selfAccount.id,
597
+ child: MessageListPage (initNarrow: const CombinedFeedNarrow ())));
598
+
599
+ await tester.pumpAndSettle ();
600
+ }
601
+
602
+ testWidgets ('no hero animation occurs between different message list pages for same image' , (tester) async {
603
+ prepareBoringImageHttpClient ();
604
+
605
+ await setupMessageListPage (tester);
606
+
607
+ final imageFinder = find.byType (RealmContentNetworkImage ).first;
608
+ final initialImageRect = tester.getRect (imageFinder);
609
+
610
+ connection.prepare (json:
611
+ eg.newestGetMessagesResult (foundOldest: true , messages: [message]).toJson ());
612
+
613
+ await tester.tap (find.descendant (
614
+ of: find.byType (StreamMessageRecipientHeader ),
615
+ matching: find.text ('test topic' )));
616
+ await tester.pump ();
617
+ // Pump halfway through the hero animation duration (300ms)
618
+ await tester.pump (const Duration (milliseconds: 150 ));
619
+
620
+ final imageInTransition = tester.getRect (imageFinder);
621
+ check (imageInTransition).top.equals (initialImageRect.top);
622
+ check (imageInTransition).left.equals (initialImageRect.left);
623
+ await tester.pumpAndSettle ();
624
+ debugNetworkImageHttpClientProvider = null ;
625
+ });
626
+
627
+ testWidgets ('hero animation occurs when opening lightbox from message list' , (tester) async {
628
+ prepareBoringImageHttpClient ();
629
+
630
+ await setupMessageListPage (tester);
631
+
632
+ final imageFinder = find.byType (RealmContentNetworkImage ).first;
633
+ final initialImageRect = tester.getRect (imageFinder);
634
+
635
+ await tester.tap (imageFinder);
636
+ await tester.pump ();
637
+ // Pump halfway through the hero animation duration (300ms)
638
+ await tester.pump (const Duration (milliseconds: 150 ));
639
+
640
+ final imageInTransition = tester.getRect (imageFinder);
641
+ check (imageInTransition).top.not ((it) => it.equals (initialImageRect.top));
642
+ check (imageInTransition).left.not ((it) => it.equals (initialImageRect.left));
643
+ await tester.pumpAndSettle ();
644
+ debugNetworkImageHttpClientProvider = null ;
645
+ });
646
+ });
561
647
}
0 commit comments