| // 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. | |
| /// @docImport 'widget_tester.dart'; | |
| library; | |
| 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.onGenerateInitialRoutes, | |
| this.onUnknownRoute, | |
| 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, | |
| this.localizationsDelegates, | |
| this.localeListResolutionCallback, | |
| }); | |
| /// 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; | |
| /// The routes generator callback used for generating initial routes if | |
| /// [initialRoute] is provided. | |
| /// | |
| /// If this property is not set, the underlying | |
| /// [Navigator.onGenerateInitialRoutes] will default to | |
| /// [Navigator.defaultGenerateInitialRoutes]. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [WidgetsApp.onGenerateInitialRoutes], the equivalent property in [WidgetsApp]. | |
| final InitialRouteListFactory? onGenerateInitialRoutes; | |
| /// Called when [onGenerateRoute] fails to generate a route, except for the | |
| /// [initialRoute]. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [WidgetsApp.onUnknownRoute], the equivalent property in [WidgetsApp]. | |
| final RouteFactory? onUnknownRoute; | |
| /// 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; | |
| /// The delegates for this app's [Localizations] widget. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [WidgetsApp.localizationsDelegates], the equivalent property in [WidgetsApp]. | |
| final Iterable<LocalizationsDelegate<Object?>>? localizationsDelegates; | |
| /// The callback responsible for choosing the app's locale | |
| /// when the app is started, and when the user changes the | |
| /// device's locale. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [WidgetsApp.localeListResolutionCallback], the equivalent property in [WidgetsApp]. | |
| /// * [basicLocaleListResolution], the default locale resolution algorithm. | |
| final LocaleListResolutionCallback? localeListResolutionCallback; | |
| 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, | |
| onGenerateInitialRoutes: onGenerateInitialRoutes, | |
| onUnknownRoute: onUnknownRoute, | |
| routes: routes, | |
| pageRouteBuilder: pageRouteBuilder, | |
| builder: builder, | |
| shortcuts: shortcuts, | |
| actions: actions, | |
| restorationScopeId: restorationScopeId, | |
| localizationsDelegates: localizationsDelegates, | |
| localeListResolutionCallback: localeListResolutionCallback, | |
| ); | |
| } | |
| } |