@@ -10,13 +10,22 @@ 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' ;
20
+ import 'package:zulip/widgets/store.dart' ;
16
21
22
+ import '../api/fake_api.dart' ;
17
23
import '../example_data.dart' as eg;
18
24
import '../model/binding.dart' ;
25
+ import '../model/content_test.dart' ;
26
+ import '../model/test_store.dart' ;
19
27
import '../test_images.dart' ;
28
+ import '../test_navigation.dart' ;
20
29
import 'dialog_checks.dart' ;
21
30
import 'test_app.dart' ;
22
31
@@ -197,6 +206,39 @@ class FakeVideoPlayerPlatform extends Fake
197
206
void main () {
198
207
TestZulipBinding .ensureInitialized ();
199
208
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
+
200
242
group ('_ImageLightboxPage' , () {
201
243
final src = Uri .parse ('https://chat.example/lightbox-image.png' );
202
244
@@ -207,14 +249,19 @@ void main() {
207
249
addTearDown (testBinding.reset);
208
250
await testBinding.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
209
251
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
+ ));
214
260
await tester.pump ();
215
261
final navigator = await ZulipApp .navigator;
216
262
unawaited (navigator.push (getImageLightboxRoute (
217
263
accountId: eg.selfAccount.id,
264
+ pageContext: PageRoot .contextOf (navigator.context),
218
265
message: message ?? eg.streamMessage (),
219
266
src: src,
220
267
thumbnailUrl: thumbnailUrl,
@@ -558,4 +605,95 @@ void main() {
558
605
check (platform.position).equals (position);
559
606
});
560
607
});
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