// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('TestNavigatorObserver gets the transition duration of the most recent transition', ( WidgetTester tester, ) async { final observer = TransitionDurationObserver(); await tester.pumpWidget( MaterialApp( navigatorObservers: [observer], onGenerateRoute: (RouteSettings settings) { return switch (settings.name) { // A route that uses FadeForwardsPageTransitionsBuilder. '/' => _TestTransitionRoute( pageTransitionsBuilder: const FadeForwardsPageTransitionsBuilder(), builder: (BuildContext context) { return Scaffold( body: Center( child: Column( children: [ const Text('Page 1'), TextButton( onPressed: () { Navigator.pushNamed(context, '/2'); }, child: const Text('Next'), ), ], ), ), ); }, ), // A route that uses ZoomPageTransitionsBuilder with custom durations. '/2' => _TestTransitionRoute( pageTransitionsBuilder: const ZoomPageTransitionsBuilder(), transitionDurationOverride: const Duration(milliseconds: 456), reverseTransitionDurationOverride: const Duration(milliseconds: 567), builder: (BuildContext context) { return Scaffold( body: Center( child: Column( children: [ const Text('Page 2'), TextButton( onPressed: () { Navigator.pushNamed(context, '/3'); }, child: const Text('Next'), ), TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Back'), ), ], ), ), ); }, ), // A route that uses FadeForwardsPageTransitionsBuilder with custom durations. '/3' => _TestTransitionRoute( pageTransitionsBuilder: const FadeForwardsPageTransitionsBuilder(), transitionDurationOverride: const Duration(milliseconds: 678), reverseTransitionDurationOverride: const Duration(milliseconds: 789), builder: (BuildContext context) { return Scaffold( body: Center( child: Column( children: [ const Text('Page 3'), TextButton( onPressed: () { Navigator.pushNamed(context, '/4'); }, child: const Text('Next'), ), TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Back'), ), ], ), ), ); }, ), // A route that uses ZoomPageTransitionsBuilder. '/4' => _TestTransitionRoute( pageTransitionsBuilder: const ZoomPageTransitionsBuilder(), builder: (BuildContext context) { return Scaffold( body: Center( child: Column( children: [ const Text('Page 4'), TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Back'), ), ], ), ), ); }, ), _ => throw Exception('Invalid route.'), }; }, ), ); expect(find.text('Page 1'), findsOneWidget); expect(find.text('Page 2'), findsNothing); expect(find.text('Page 3'), findsNothing); expect(find.text('Page 4'), findsNothing); expect( observer.transitionDuration, const FadeForwardsPageTransitionsBuilder().transitionDuration, ); await tester.tap(find.text('Next')); expect(observer.transitionDuration, const Duration(milliseconds: 456)); await observer.pumpPastTransition(tester); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), findsOneWidget); expect(find.text('Page 3'), findsNothing); expect(find.text('Page 4'), findsNothing); await tester.tap(find.text('Next')); expect(observer.transitionDuration, const Duration(milliseconds: 678)); await observer.pumpPastTransition(tester); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), findsNothing); expect(find.text('Page 3'), findsOneWidget); expect(find.text('Page 4'), findsNothing); await tester.tap(find.text('Next')); expect(observer.transitionDuration, const ZoomPageTransitionsBuilder().transitionDuration); await observer.pumpPastTransition(tester); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), findsNothing); expect(find.text('Page 3'), findsNothing); expect(find.text('Page 4'), findsOneWidget); await tester.tap(find.text('Back')); expect( observer.transitionDuration, const ZoomPageTransitionsBuilder().reverseTransitionDuration, ); await observer.pumpPastTransition(tester); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), findsNothing); expect(find.text('Page 3'), findsOneWidget); expect(find.text('Page 4'), findsNothing); await tester.tap(find.text('Back')); expect(observer.transitionDuration, const Duration(milliseconds: 789)); await observer.pumpPastTransition(tester); expect(find.text('Page 1'), findsNothing); expect(find.text('Page 2'), findsOneWidget); expect(find.text('Page 3'), findsNothing); expect(find.text('Page 4'), findsNothing); await tester.tap(find.text('Back')); expect(observer.transitionDuration, const Duration(milliseconds: 567)); await observer.pumpPastTransition(tester); expect(find.text('Page 1'), findsOneWidget); expect(find.text('Page 2'), findsNothing); expect(find.text('Page 3'), findsNothing); expect(find.text('Page 4'), findsNothing); }); testWidgets('TestNavigatorObserver throws when there has never been a transition', ( WidgetTester tester, ) async { final observer = TransitionDurationObserver(); await tester.pumpWidget( MaterialApp( navigatorObservers: [observer], onGenerateRoute: (RouteSettings settings) { return switch (settings.name) { // A route with no transition. '/' => _TestOverlayRoute( builder: (BuildContext context) { return const Scaffold( body: Center(child: Column(children: [Text('Page 1')])), ); }, ), _ => throw Exception('Invalid route.'), }; }, ), ); expect(find.text('Page 1'), findsOneWidget); expect(() => observer.transitionDuration, throwsA(isFlutterError)); }); } class _TestTransitionRoute extends MaterialPageRoute { _TestTransitionRoute({ required super.builder, required this.pageTransitionsBuilder, this.transitionDurationOverride, this.reverseTransitionDurationOverride, }); final PageTransitionsBuilder pageTransitionsBuilder; final Duration? transitionDurationOverride; final Duration? reverseTransitionDurationOverride; @override Widget buildTransitions( BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { return pageTransitionsBuilder.buildTransitions( this, context, animation, secondaryAnimation, child, ); } @override Duration get transitionDuration => transitionDurationOverride ?? pageTransitionsBuilder.transitionDuration; @override Duration get reverseTransitionDuration => reverseTransitionDurationOverride ?? pageTransitionsBuilder.reverseTransitionDuration; } class _TestOverlayRoute extends OverlayRoute { _TestOverlayRoute({required this.builder}); final WidgetBuilder builder; @override Iterable createOverlayEntries() { return [OverlayEntry(builder: builder)]; } }