| // 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/widgets.dart'; |
| |
| /// A minimal [WidgetsApp] wrapper for use in widget tests. |
| /// |
| /// This provides a convenient way to wrap test widgets with the necessary |
| /// app-level widgets (like [Directionality], [MediaQuery], etc.) that |
| /// [WidgetsApp] provides, without the overhead of `MaterialApp` or `CupertinoApp`. |
| /// |
| /// The [pageRouteBuilder] creates a [Navigator] which provides an [Overlay], |
| /// so widgets that need overlay support (like [Draggable], [RawTooltip], etc.) |
| /// will work correctly when placed in [home]. |
| /// |
| /// Example usage: |
| /// ```dart |
| /// testWidgets('my test', (WidgetTester tester) async { |
| /// await tester.pumpWidget( |
| /// TestWidgetsApp( |
| /// home: GestureDetector( |
| /// onTap: () {}, |
| /// child: Container(), |
| /// ), |
| /// ), |
| /// ); |
| /// }); |
| /// ``` |
| /// |
| /// For navigation tests with routes: |
| /// ```dart |
| /// testWidgets('navigation test', (WidgetTester tester) async { |
| /// await tester.pumpWidget( |
| /// TestWidgetsApp( |
| /// home: const Text('Home'), |
| /// routes: <String, WidgetBuilder>{ |
| /// '/details': (BuildContext context) => const Text('Details'), |
| /// }, |
| /// ), |
| /// ); |
| /// tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/details'); |
| /// await tester.pumpAndSettle(); |
| /// }); |
| /// ``` |
| /// |
| /// For tests that need full control over route generation: |
| /// ```dart |
| /// testWidgets('custom route test', (WidgetTester tester) async { |
| /// await tester.pumpWidget( |
| /// TestWidgetsApp( |
| /// initialRoute: '/', |
| /// onGenerateRoute: (RouteSettings settings) { |
| /// return PageRouteBuilder<void>( |
| /// settings: settings, |
| /// pageBuilder: (_, _, _) => const Text('Generated'), |
| /// ); |
| /// }, |
| /// ), |
| /// ); |
| /// }); |
| /// ``` |
| class TestWidgetsApp extends StatelessWidget { |
| /// Creates a minimal [WidgetsApp] for testing. |
| const TestWidgetsApp({ |
| super.key, |
| this.navigatorKey, |
| this.home, |
| this.initialRoute, |
| this.onGenerateRoute, |
| this.navigatorObservers = const <NavigatorObserver>[], |
| this.routes = const <String, WidgetBuilder>{}, |
| this.color = const Color(0xFFFFFFFF), |
| this.textStyle, |
| this.pageRouteBuilder = _defaultPageRouteBuilder, |
| this.builder, |
| this.shortcuts, |
| this.actions, |
| this.restorationScopeId, |
| }); |
| |
| /// A key to use when building the [Navigator]. |
| /// |
| /// In tests, this allows direct access to the [NavigatorState] for |
| /// programmatic navigation without needing to find the [Navigator] widget: |
| /// |
| /// ```dart |
| /// import 'package:flutter/widgets.dart'; |
| /// import 'package:flutter_test/flutter_test.dart'; |
| /// |
| /// void main() { |
| /// testWidgets('widgets app can use navigator test', (WidgetTester tester) async { |
| /// final navigatorKey = GlobalKey<NavigatorState>(); |
| /// |
| /// await tester.pumpWidget(TestWidgetsApp( |
| /// navigatorKey: navigatorKey, |
| /// home: const Text('Home'), |
| /// )); |
| /// |
| /// navigatorKey.currentState!.pushNamed('/details'); |
| /// }); |
| /// } |
| /// ``` |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.navigatorKey], the equivalent property in [WidgetsApp]. |
| final GlobalKey<NavigatorState>? navigatorKey; |
| |
| /// The widget displayed when the app launches. |
| /// |
| /// In tests, this is where you place the widget under test. The widget |
| /// will have access to [Navigator], [Overlay], and other app-level |
| /// services provided by [WidgetsApp]. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.home], the equivalent property in [WidgetsApp]. |
| final Widget? home; |
| |
| /// The name of the first route to show when the app launches. |
| /// |
| /// Defaults to [Navigator.defaultRouteName] (typically `/`). |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.initialRoute], the equivalent property in [WidgetsApp]. |
| final String? initialRoute; |
| |
| /// The route generator callback used when the app is navigated to a named |
| /// route. |
| /// |
| /// This callback is used if [routes] and [home] do not contain the requested |
| /// route. |
| /// |
| /// The [pageRouteBuilder] is not used for routes created by this callback. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.onGenerateRoute], the equivalent property in [WidgetsApp]. |
| final RouteFactory? onGenerateRoute; |
| |
| /// A list of [NavigatorObserver] for the app's [Navigator]. |
| /// |
| /// Defaults to an empty list. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.navigatorObservers], the equivalent property in [WidgetsApp]. |
| final List<NavigatorObserver> navigatorObservers; |
| |
| /// The application's top-level routing table. |
| /// |
| /// Maps route names to widget builders. When navigating to a named route, |
| /// the corresponding builder is called to create the route's content. |
| /// |
| /// In tests, routes are built using [pageRouteBuilder], which defaults to |
| /// zero-duration transitions for instant navigation without waiting for |
| /// animations. |
| /// |
| /// Defaults to an empty map. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.routes], the equivalent property in [WidgetsApp]. |
| final Map<String, WidgetBuilder> routes; |
| |
| /// The primary color to use for the application in the operating system |
| /// interface. |
| /// |
| /// This is typically unused in tests as OS-level features are not |
| /// exercised. |
| /// |
| /// Defaults to white. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.color], the equivalent property in [WidgetsApp]. |
| final Color color; |
| |
| /// The default text style for [Text] widgets in the app. |
| /// |
| /// Passed directly to [WidgetsApp.textStyle], which wraps the widget tree |
| /// in a [DefaultTextStyle]. |
| final TextStyle? textStyle; |
| |
| /// A function that creates page routes for named navigation. |
| /// |
| /// Defaults to a [PageRouteBuilder] with no transition animation, allowing |
| /// instant navigation without calling [WidgetTester.pumpAndSettle]. |
| /// |
| /// Override this to customize route behavior or test specific transitions: |
| /// |
| /// ```dart |
| /// TestWidgetsApp( |
| /// pageRouteBuilder: <T>(settings, builder) { |
| /// return PageRouteBuilder<T>( |
| /// settings: settings, |
| /// transitionDuration: const Duration(milliseconds: 300), |
| /// pageBuilder: (context, _, _) => builder(context), |
| /// transitionsBuilder: (_, animation, _, child) { |
| /// return FadeTransition(opacity: animation, child: child); |
| /// }, |
| /// ); |
| /// }, |
| /// home: const Text('Home'), |
| /// ) |
| /// ``` |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.pageRouteBuilder], the equivalent property in [WidgetsApp]. |
| final PageRouteFactory pageRouteBuilder; |
| |
| /// A builder for inserting widgets above the [Navigator]. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.builder], the equivalent property in [WidgetsApp]. |
| final TransitionBuilder? builder; |
| |
| /// The application's keyboard shortcut map. |
| /// |
| /// In tests, this allows registering custom keyboard shortcuts to verify |
| /// that key combinations trigger the expected [Intent]s. |
| /// |
| /// When null, [WidgetsApp.defaultShortcuts] are used. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.shortcuts], the equivalent property in [WidgetsApp]. |
| final Map<ShortcutActivator, Intent>? shortcuts; |
| |
| /// The application's action map. |
| /// |
| /// In tests, this allows registering custom [Action]s that respond to |
| /// [Intent]s dispatched by [Shortcuts] or programmatic invocation. |
| /// |
| /// When null, [WidgetsApp.defaultActions] are used. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.actions], the equivalent property in [WidgetsApp]. |
| final Map<Type, Action<Intent>>? actions; |
| |
| /// The identifier to use for state restoration of the app's [Navigator]. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsApp.restorationScopeId], the equivalent property in [WidgetsApp]. |
| final String? restorationScopeId; |
| |
| static PageRoute<T> _defaultPageRouteBuilder<T>(RouteSettings settings, WidgetBuilder builder) { |
| return PageRouteBuilder<T>( |
| settings: settings, |
| pageBuilder: |
| ( |
| BuildContext context, |
| Animation<double> animation, |
| Animation<double> secondaryAnimation, |
| ) => builder(context), |
| transitionsBuilder: |
| ( |
| BuildContext context, |
| Animation<double> animation, |
| Animation<double> secondaryAnimation, |
| Widget child, |
| ) => child, |
| ); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return WidgetsApp( |
| color: color, |
| textStyle: textStyle, |
| navigatorKey: navigatorKey, |
| navigatorObservers: navigatorObservers, |
| home: home, |
| initialRoute: initialRoute, |
| onGenerateRoute: onGenerateRoute, |
| routes: routes, |
| pageRouteBuilder: pageRouteBuilder, |
| builder: builder, |
| shortcuts: shortcuts, |
| actions: actions, |
| restorationScopeId: restorationScopeId, |
| ); |
| } |
| } |