From a3f18204c83532ac6e12651e67e518f39d502568 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Sun, 3 Aug 2025 14:34:38 +0200 Subject: [PATCH 1/7] Add RelativeGoRouteData and TypedRelativeGoRoute --- packages/go_router/CHANGELOG.md | 6 +- packages/go_router/lib/src/route_data.dart | 317 ++++++++++++++++--- packages/go_router/pubspec.yaml | 2 +- packages/go_router/test/route_data_test.dart | 293 ++++++++++++++--- 4 files changed, 520 insertions(+), 98 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 413efb10461..089de3e9efe 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,8 +1,10 @@ -## NEXT +## 16.1.1 -* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +- Adds `RelativeGoRouteData` and `TypedRelativeGoRoute`. +- Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 16.1.0 + - Adds annotation for go_router_builder that enable custom string encoder/decoder [#110781](https://github.com/flutter/flutter/issues/110781). **Requires go_router_builder >= 3.1.0**. ## 16.0.0 diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index c8e30bacd68..c7c653b840a 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:meta/meta_meta.dart'; +import 'configuration.dart'; import 'route.dart'; import 'state.dart'; @@ -18,13 +19,95 @@ abstract class RouteData { const RouteData(); } +/// A base class for [GoRouteData] and [RelativeGoRouteData] that provides +/// common functionality for type-safe routing. +abstract class _GoRouteData extends RouteData { + const _GoRouteData(); + + /// Creates the [Widget] for `this` route. + Widget build(BuildContext context, GoRouterState state) => + throw UnimplementedError( + 'One of `build` or `buildPage` must be implemented.', + ); + + /// A page builder for this route. + Page buildPage(BuildContext context, GoRouterState state) => + const NoOpPage(); + + /// An optional redirect function for this route. + FutureOr redirect(BuildContext context, GoRouterState state) => null; + + /// Called when this route is removed from GoRouter's route history. + FutureOr onExit(BuildContext context, GoRouterState state) => true; + + /// The error thrown when a user-facing method is not implemented by the + /// generated code. + static UnimplementedError get shouldBeGeneratedError => UnimplementedError( + 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).', + ); +} + +/// Helper to build a location string from a path and query parameters. +String _buildLocation(String path, {Map? queryParams}) => + Uri.parse(path) + .replace( + queryParameters: + // Avoid `?` in generated location if `queryParams` is empty + queryParams?.isNotEmpty ?? false ? queryParams : null, + ) + .toString(); + +/// Holds the parameters for constructing a [GoRoute]. +class _GoRouteParameters { + const _GoRouteParameters({ + required this.builder, + required this.pageBuilder, + required this.redirect, + required this.onExit, + }); + + final GoRouterWidgetBuilder builder; + final GoRouterPageBuilder pageBuilder; + final GoRouterRedirect redirect; + final ExitCallback onExit; +} + +/// Helper to create [GoRoute] parameters from a factory function and an Expando. +_GoRouteParameters _createGoRouteParameters({ + required T Function(GoRouterState) factory, + required Expando<_GoRouteData> expando, +}) { + T factoryImpl(GoRouterState state) { + final Object? extra = state.extra; + + // If the "extra" value is of type `T` then we know it's the source + // instance, so it doesn't need to be recreated. + if (extra is T) { + return extra; + } + + return (expando[state] ??= factory(state)) as T; + } + + return _GoRouteParameters( + builder: (BuildContext context, GoRouterState state) => + factoryImpl(state).build(context, state), + pageBuilder: (BuildContext context, GoRouterState state) => + factoryImpl(state).buildPage(context, state), + redirect: (BuildContext context, GoRouterState state) => + factoryImpl(state).redirect(context, state), + onExit: (BuildContext context, GoRouterState state) => + factoryImpl(state).onExit(context, state), + ); +} + /// A class to represent a [GoRoute] in /// [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html). /// /// Subclasses must override one of [build], [buildPage], or /// [redirect]. /// {@category Type-safe routes} -abstract class GoRouteData extends RouteData { +abstract class GoRouteData extends _GoRouteData { /// Allows subclasses to have `const` constructors. /// /// [GoRouteData] is abstract and cannot be instantiated directly. @@ -36,10 +119,9 @@ abstract class GoRouteData extends RouteData { /// [redirect]. /// /// Corresponds to [GoRoute.builder]. + @override Widget build(BuildContext context, GoRouterState state) => - throw UnimplementedError( - 'One of `build` or `buildPage` must be implemented.', - ); + super.build(context, state); /// A page builder for this route. /// @@ -52,8 +134,9 @@ abstract class GoRouteData extends RouteData { /// /// By default, returns a [Page] instance that is ignored, causing a default /// [Page] implementation to be used with the results of [build]. + @override Page buildPage(BuildContext context, GoRouterState state) => - const NoOpPage(); + super.buildPage(context, state); /// An optional redirect function for this route. /// @@ -61,24 +144,22 @@ abstract class GoRouteData extends RouteData { /// [redirect]. /// /// Corresponds to [GoRoute.redirect]. - FutureOr redirect(BuildContext context, GoRouterState state) => null; + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + super.redirect(context, state); /// Called when this route is removed from GoRouter's route history. /// /// Corresponds to [GoRoute.onExit]. - FutureOr onExit(BuildContext context, GoRouterState state) => true; + @override + FutureOr onExit(BuildContext context, GoRouterState state) => + super.onExit(context, state); /// A helper function used by generated code. /// /// Should not be used directly. static String $location(String path, {Map? queryParams}) => - Uri.parse(path) - .replace( - queryParameters: - // Avoid `?` in generated location if `queryParams` is empty - queryParams?.isNotEmpty ?? false ? queryParams : null, - ) - .toString(); + _buildLocation(path, queryParams: queryParams); /// A helper function used by generated code. /// @@ -91,60 +172,166 @@ abstract class GoRouteData extends RouteData { GlobalKey? parentNavigatorKey, List routes = const [], }) { - T factoryImpl(GoRouterState state) { - final Object? extra = state.extra; + final _GoRouteParameters params = _createGoRouteParameters( + factory: factory, + expando: _stateObjectExpando, + ); - // If the "extra" value is of type `T` then we know it's the source - // instance of `GoRouteData`, so it doesn't need to be recreated. - if (extra is T) { - return extra; - } + return GoRoute( + path: path, + name: name, + caseSensitive: caseSensitive, + builder: params.builder, + pageBuilder: params.pageBuilder, + redirect: params.redirect, + routes: routes, + parentNavigatorKey: parentNavigatorKey, + onExit: params.onExit, + ); + } - return (_stateObjectExpando[state] ??= factory(state)) as T; - } + /// Used to cache [GoRouteData] that corresponds to a given [GoRouterState] + /// to minimize the number of times it has to be deserialized. + static final Expando<_GoRouteData> _stateObjectExpando = + Expando<_GoRouteData>( + 'GoRouteState to GoRouteData expando', + ); - Widget builder(BuildContext context, GoRouterState state) => - factoryImpl(state).build(context, state); + /// The location of this route, e.g. /family/f2/person/p1 + String get location => throw _GoRouteData.shouldBeGeneratedError; - Page pageBuilder(BuildContext context, GoRouterState state) => - factoryImpl(state).buildPage(context, state); + /// Navigate to the route. + void go(BuildContext context) => throw _GoRouteData.shouldBeGeneratedError; - FutureOr redirect(BuildContext context, GoRouterState state) => - factoryImpl(state).redirect(context, state); + /// Push the route onto the page stack. + Future push(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; + + /// Replaces the top-most page of the page stack with the route. + void pushReplacement(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; + + /// Replaces the top-most page of the page stack with the route but treats + /// it as the same page. + /// + /// The page key will be reused. This will preserve the state and not run any + /// page animation. + /// + void replace(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; +} + +/// A class to represent a relative [GoRoute] in +/// [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html). +/// +/// Subclasses must override one of [build], [buildPage], or +/// [redirect]. +/// {@category Type-safe routes} +abstract class RelativeGoRouteData extends _GoRouteData { + /// Allows subclasses to have `const` constructors. + /// + /// [RelativeGoRouteData] is abstract and cannot be instantiated directly. + const RelativeGoRouteData(); + + /// Creates the [Widget] for `this` route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.builder]. + @override + Widget build(BuildContext context, GoRouterState state) => + super.build(context, state); - FutureOr onExit(BuildContext context, GoRouterState state) => - factoryImpl(state).onExit(context, state); + /// A page builder for this route. + /// + /// Subclasses can override this function to provide a custom [Page]. + /// + /// Subclasses must override one of [build], [buildPage] or + /// [redirect]. + /// + /// Corresponds to [GoRoute.pageBuilder]. + /// + /// By default, returns a [Page] instance that is ignored, causing a default + /// [Page] implementation to be used with the results of [build]. + @override + Page buildPage(BuildContext context, GoRouterState state) => + super.buildPage(context, state); + + /// An optional redirect function for this route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.redirect]. + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + super.redirect(context, state); + + /// Called when this route is removed from GoRouter's route history. + /// + /// Corresponds to [GoRoute.onExit]. + @override + FutureOr onExit(BuildContext context, GoRouterState state) => + super.onExit(context, state); + + /// A helper function used by generated code. + /// + /// Should not be used directly. + static String $location(String path, {Map? queryParams}) => + _buildLocation(path, queryParams: queryParams); + + /// A helper function used by generated code. + /// + /// Should not be used directly. + static GoRoute $route({ + required String path, + bool caseSensitive = true, + required T Function(GoRouterState) factory, + GlobalKey? parentNavigatorKey, + List routes = const [], + }) { + final _GoRouteParameters params = _createGoRouteParameters( + factory: factory, + expando: _stateObjectExpando, + ); return GoRoute( path: path, - name: name, caseSensitive: caseSensitive, - builder: builder, - pageBuilder: pageBuilder, - redirect: redirect, + builder: params.builder, + pageBuilder: params.pageBuilder, + redirect: params.redirect, routes: routes, parentNavigatorKey: parentNavigatorKey, - onExit: onExit, + onExit: params.onExit, ); } - /// Used to cache [GoRouteData] that corresponds to a given [GoRouterState] + /// Used to cache [RelativeGoRouteData] that corresponds to a given [GoRouterState] /// to minimize the number of times it has to be deserialized. - static final Expando _stateObjectExpando = Expando( - 'GoRouteState to GoRouteData expando', + static final Expando<_GoRouteData> _stateObjectExpando = + Expando<_GoRouteData>( + 'GoRouteState to RelativeGoRouteData expando', ); - /// The location of this route. - String get location => throw _shouldBeGeneratedError; + /// The location of this route, e.g. person/p1 + String get location => throw _GoRouteData.shouldBeGeneratedError; + + /// The relative location of this route, e.g. ./person/p1 + String get relativeLocation => throw _GoRouteData.shouldBeGeneratedError; /// Navigate to the route. - void go(BuildContext context) => throw _shouldBeGeneratedError; + void goRelative(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; /// Push the route onto the page stack. - Future push(BuildContext context) => throw _shouldBeGeneratedError; + Future pushRelative(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; /// Replaces the top-most page of the page stack with the route. - void pushReplacement(BuildContext context) => throw _shouldBeGeneratedError; + void pushReplacementRelative(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; /// Replaces the top-most page of the page stack with the route but treats /// it as the same page. @@ -152,11 +339,8 @@ abstract class GoRouteData extends RouteData { /// The page key will be reused. This will preserve the state and not run any /// page animation. /// - void replace(BuildContext context) => throw _shouldBeGeneratedError; - - static UnimplementedError get _shouldBeGeneratedError => UnimplementedError( - 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).', - ); + void replaceRelative(BuildContext context) => + throw _GoRouteData.shouldBeGeneratedError; } /// A class to represent a [ShellRoute] in @@ -402,6 +586,41 @@ class TypedGoRoute extends TypedRoute { final bool caseSensitive; } +/// A superclass for each typed relative go route descendant +@Target({TargetKind.library, TargetKind.classType}) +class TypedRelativeGoRoute + extends TypedRoute { + /// Default const constructor + const TypedRelativeGoRoute({ + required this.path, + this.routes = const >[], + this.caseSensitive = true, + }); + + /// The relative path that corresponds to this route. + /// + /// See [GoRoute.path]. + /// + /// + final String path; + + /// Child route definitions. + /// + /// See [RouteBase.routes]. + final List> routes; + + /// Determines whether the route matching is case sensitive. + /// + /// When `true`, the path must match the specified case. For example, + /// a route with `path: '/family/:fid'` will not match `/FaMiLy/f2`. + /// + /// When `false`, the path matching is case insensitive. The route + /// with `path: '/family/:fid'` will match `/FaMiLy/f2`. + /// + /// Defaults to `true`. + final bool caseSensitive; +} + /// A superclass for each typed shell route descendant @Target({TargetKind.library, TargetKind.classType}) class TypedShellRoute extends TypedRoute { diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 24064f28914..f163f276e4c 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,7 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 16.1.0 +version: 16.1.1 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 61b31e7cb8c..e4b773cf670 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -17,6 +17,14 @@ class _GoRouteDataBuild extends GoRouteData { const SizedBox(key: Key('build')); } +class _RelativeGoRouteDataBuild extends RelativeGoRouteData { + const _RelativeGoRouteDataBuild(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const SizedBox(key: Key('build')); +} + class _ShellRouteDataRedirectPage extends ShellRouteData { const _ShellRouteDataRedirectPage(); @@ -57,6 +65,11 @@ final GoRoute _goRouteDataBuild = GoRouteData.$route( factory: (GoRouterState state) => const _GoRouteDataBuild(), ); +final GoRoute _relativeGoRouteDataBuild = RelativeGoRouteData.$route( + path: 'build', + factory: (GoRouterState state) => const _RelativeGoRouteDataBuild(), +); + final ShellRoute _shellRouteDataBuilder = ShellRouteData.$route( factory: (GoRouterState state) => const _ShellRouteDataBuilder(), routes: [ @@ -75,6 +88,26 @@ class _GoRouteDataBuildPage extends GoRouteData { const MaterialPage(child: SizedBox(key: Key('buildPage'))); } +class _RelativeGoRouteDataBuildPage extends RelativeGoRouteData { + const _RelativeGoRouteDataBuildPage(); + + @override + Page buildPage(BuildContext context, GoRouterState state) => + const MaterialPage( + child: SizedBox(key: Key('buildPage')), + ); +} + +class _RelativeGoRouteDataBuildPage extends RelativeGoRouteData { + const _RelativeGoRouteDataBuildPage(); + + @override + Page buildPage(BuildContext context, GoRouterState state) => + const MaterialPage( + child: SizedBox(key: Key('buildPage')), + ); +} + class _ShellRouteDataPageBuilder extends ShellRouteData { const _ShellRouteDataPageBuilder(); @@ -101,6 +134,11 @@ final GoRoute _goRouteDataBuildPage = GoRouteData.$route( factory: (GoRouterState state) => const _GoRouteDataBuildPage(), ); +final GoRoute _relativeGoRouteDataBuildPage = RelativeGoRouteData.$route( + path: 'build-page', + factory: (GoRouterState state) => const _RelativeGoRouteDataBuildPage(), +); + final ShellRoute _shellRouteDataPageBuilder = ShellRouteData.$route( factory: (GoRouterState state) => const _ShellRouteDataPageBuilder(), routes: [ @@ -189,11 +227,24 @@ class _GoRouteDataRedirectPage extends GoRouteData { '/build-page'; } +class _RelativeGoRouteDataRedirectPage extends RelativeGoRouteData { + const _RelativeGoRouteDataRedirectPage(); + + @override + FutureOr redirect(BuildContext context, GoRouterState state) => + '/build-page'; +} + final GoRoute _goRouteDataRedirect = GoRouteData.$route( path: '/redirect', factory: (GoRouterState state) => const _GoRouteDataRedirectPage(), ); +final GoRoute _relativeGoRouteDataRedirect = RelativeGoRouteData.$route( + path: 'redirect', + factory: (GoRouterState state) => const _RelativeGoRouteDataRedirectPage(), +); + final List _routes = [ _goRouteDataBuild, _goRouteDataBuildPage, @@ -208,6 +259,18 @@ String toBase64(String value) { return base64.encode(const Utf8Encoder().convert(value)); } +final List _relativeRoutes = [ + GoRouteData.$route( + path: '/', + factory: (GoRouterState state) => const _GoRouteDataBuild(), + routes: [ + _relativeGoRouteDataBuild, + _relativeGoRouteDataBuildPage, + _relativeGoRouteDataRedirect, + ], + ), +]; + void main() { group('GoRouteData', () { testWidgets('It should build the page from the overridden build method', ( @@ -262,71 +325,166 @@ void main() { }, ); - testWidgets('It should throw beacuase there is no code generated', ( - WidgetTester tester, - ) async { - final List errors = []; + testWidgets( + 'It should throw because there is no code generated', + (WidgetTester tester) async { + final List errors = []; FlutterError.onError = (FlutterErrorDetails details) => errors.add(details); const String errorText = 'Should be generated'; - Widget buildWidget(void Function(BuildContext) onTap) { - return MaterialApp( - home: Builder( - builder: - (BuildContext context) => GestureDetector( - child: const Text('Tap'), - onTap: () => onTap(context), - ), - ), + Future expectUnimplementedError( + void Function(BuildContext) onTap) async { + await tester.pumpWidget(MaterialApp( + home: Builder( + builder: (BuildContext context) => GestureDetector( + child: const Text('Tap'), + onTap: () => onTap(context), + ), + ), + )); + await tester.tap(find.text('Tap')); + + expect(errors.first.exception, isA()); + expect(errors.first.exception.toString(), contains(errorText)); + + errors.clear(); + } + + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().location; + }); + + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().push(context); + }); + + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().go(context); + }); + + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().pushReplacement(context); + }); + + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().replace(context); + }); + + FlutterError.onError = FlutterError.dumpErrorToConsole; + }, + ); + }); + + group('RelativeGoRouteData', () { + testWidgets( + 'It should build the page from the overridden build method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/build', + routes: _relativeRoutes, + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsOneWidget); + expect(find.byKey(const Key('buildPage')), findsNothing); + }, + ); + + testWidgets( + 'It should build the page from the overridden buildPage method', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/build-page', + routes: _relativeRoutes, + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsNothing); + expect(find.byKey(const Key('buildPage')), findsOneWidget); + }, + ); + + testWidgets( + 'It should build a go route with the default case sensitivity', + (WidgetTester tester) async { + final GoRoute routeWithDefaultCaseSensitivity = + RelativeGoRouteData.$route( + path: 'path', + factory: (GoRouterState state) => const _RelativeGoRouteDataBuild(), ); - } - final Widget pushThrower = buildWidget((BuildContext context) { - const _GoRouteDataBuild().push(context); - }); - await tester.pumpWidget(pushThrower); - await tester.tap(find.text('Tap')); + expect(routeWithDefaultCaseSensitivity.caseSensitive, true); + }, + ); + + testWidgets( + 'It should build a go route with the overridden case sensitivity', + (WidgetTester tester) async { + final GoRoute routeWithDefaultCaseSensitivity = + RelativeGoRouteData.$route( + path: 'path', + caseSensitive: false, + factory: (GoRouterState state) => const _RelativeGoRouteDataBuild(), + ); + + expect(routeWithDefaultCaseSensitivity.caseSensitive, false); + }, + ); + + testWidgets( + 'It should throw because there is no code generated', + (WidgetTester tester) async { + final List errors = []; - expect(errors.first.exception, isA()); - expect(errors.first.exception.toString(), contains(errorText)); + FlutterError.onError = + (FlutterErrorDetails details) => errors.add(details); - errors.clear(); + const String errorText = 'Should be generated'; - final Widget goThrower = buildWidget((BuildContext context) { - const _GoRouteDataBuild().go(context); - }); - await tester.pumpWidget(goThrower); - await tester.tap(find.text('Tap')); + Future expectUnimplementedError( + void Function(BuildContext) onTap) async { + await tester.pumpWidget(MaterialApp( + home: Builder( + builder: (BuildContext context) => GestureDetector( + child: const Text('Tap'), + onTap: () => onTap(context), + ), + ), + )); + await tester.tap(find.text('Tap')); - expect(errors.first.exception, isA()); - expect(errors.first.exception.toString(), contains(errorText)); + expect(errors.first.exception, isA()); + expect(errors.first.exception.toString(), contains(errorText)); - errors.clear(); + errors.clear(); + } - final Widget pushReplacementThrower = buildWidget((BuildContext context) { - const _GoRouteDataBuild().pushReplacement(context); - }); - await tester.pumpWidget(pushReplacementThrower); - await tester.tap(find.text('Tap')); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().location; + }); - expect(errors.first.exception, isA()); - expect(errors.first.exception.toString(), contains(errorText)); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().relativeLocation; + }); - errors.clear(); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().pushRelative(context); + }); - final Widget replaceThrower = buildWidget((BuildContext context) { - const _GoRouteDataBuild().pushReplacement(context); - }); - await tester.pumpWidget(replaceThrower); - await tester.tap(find.text('Tap')); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().goRelative(context); + }); - expect(errors.first.exception, isA()); - expect(errors.first.exception.toString(), contains(errorText)); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().pushReplacementRelative(context); + }); - errors.clear(); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().replaceRelative(context); + }); FlutterError.onError = FlutterError.dumpErrorToConsole; }); @@ -613,4 +771,47 @@ void main() { expect(customParameterCodec.encode, toBase64); expect(customParameterCodec.decode, fromBase64); }); + + test('TypedRelativeGoRoute with default parameters', () { + const TypedRelativeGoRoute typedGoRoute = + TypedRelativeGoRoute( + path: 'path', + ); + + expect(typedGoRoute.path, 'path'); + expect(typedGoRoute.caseSensitive, true); + expect(typedGoRoute.routes, isEmpty); + }); + + test('TypedRelativeGoRoute with provided parameters', () { + const TypedRelativeGoRoute typedGoRoute = + TypedRelativeGoRoute( + path: 'path', + caseSensitive: false, + routes: >[ + TypedRelativeGoRoute( + path: 'sub-path', + caseSensitive: false, + ), + ], + ); + + expect(typedGoRoute.path, 'path'); + expect(typedGoRoute.caseSensitive, false); + expect(typedGoRoute.routes, hasLength(1)); + expect( + typedGoRoute.routes.single, + isA>() + .having( + (TypedRelativeGoRoute route) => route.path, + 'path', + 'sub-path') + .having( + (TypedRelativeGoRoute route) => + route.caseSensitive, + 'caseSensitive', + false, + ), + ); + }); } From 63de86d43a29e7bb76f5382803d8dc414b0e5304 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Fri, 8 Aug 2025 00:50:38 +0200 Subject: [PATCH 2/7] Rename RelativeGoRouteData location to subpath --- packages/go_router/lib/src/route_data.dart | 4 ++-- packages/go_router/test/route_data_test.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index c7c653b840a..9a04dbc9952 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -315,8 +315,8 @@ abstract class RelativeGoRouteData extends _GoRouteData { 'GoRouteState to RelativeGoRouteData expando', ); - /// The location of this route, e.g. person/p1 - String get location => throw _GoRouteData.shouldBeGeneratedError; + /// The subpath of this route, e.g. person/p1 + String get subpath => throw _GoRouteData.shouldBeGeneratedError; /// The relative location of this route, e.g. ./person/p1 String get relativeLocation => throw _GoRouteData.shouldBeGeneratedError; diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index e4b773cf670..d141edd7c5e 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -463,7 +463,7 @@ void main() { } await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().location; + const _RelativeGoRouteDataBuild().subpath; }); await expectUnimplementedError((BuildContext context) { From 57eca46dcedb814790e459d018444397420c1216 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Wed, 13 Aug 2025 22:48:42 +0200 Subject: [PATCH 3/7] Rename subpath to subLocation --- packages/go_router/lib/src/route_data.dart | 4 ++-- packages/go_router/test/route_data_test.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 9a04dbc9952..eb220cb9d07 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -315,8 +315,8 @@ abstract class RelativeGoRouteData extends _GoRouteData { 'GoRouteState to RelativeGoRouteData expando', ); - /// The subpath of this route, e.g. person/p1 - String get subpath => throw _GoRouteData.shouldBeGeneratedError; + /// The sub-location of this route, e.g. person/p1 + String get subLocation => throw _GoRouteData.shouldBeGeneratedError; /// The relative location of this route, e.g. ./person/p1 String get relativeLocation => throw _GoRouteData.shouldBeGeneratedError; diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index d141edd7c5e..5bff78166cc 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -463,7 +463,7 @@ void main() { } await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().subpath; + const _RelativeGoRouteDataBuild().subLocation; }); await expectUnimplementedError((BuildContext context) { From 3b8d48c8803af2e8fe2a395b1c54bb46775630bb Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Wed, 13 Aug 2025 22:59:32 +0200 Subject: [PATCH 4/7] Share expando --- packages/go_router/lib/src/route_data.dart | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index eb220cb9d07..2bf89d94f59 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -45,6 +45,11 @@ abstract class _GoRouteData extends RouteData { static UnimplementedError get shouldBeGeneratedError => UnimplementedError( 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).', ); + + /// Used to cache [_GoRouteData] that corresponds to a given [GoRouterState] + /// to minimize the number of times it has to be deserialized. + static final Expando<_GoRouteData> stateObjectExpando = + Expando<_GoRouteData>('GoRouteState to _GoRouteData expando'); } /// Helper to build a location string from a path and query parameters. @@ -174,7 +179,7 @@ abstract class GoRouteData extends _GoRouteData { }) { final _GoRouteParameters params = _createGoRouteParameters( factory: factory, - expando: _stateObjectExpando, + expando: _GoRouteData.stateObjectExpando, ); return GoRoute( @@ -190,13 +195,6 @@ abstract class GoRouteData extends _GoRouteData { ); } - /// Used to cache [GoRouteData] that corresponds to a given [GoRouterState] - /// to minimize the number of times it has to be deserialized. - static final Expando<_GoRouteData> _stateObjectExpando = - Expando<_GoRouteData>( - 'GoRouteState to GoRouteData expando', - ); - /// The location of this route, e.g. /family/f2/person/p1 String get location => throw _GoRouteData.shouldBeGeneratedError; @@ -293,7 +291,7 @@ abstract class RelativeGoRouteData extends _GoRouteData { }) { final _GoRouteParameters params = _createGoRouteParameters( factory: factory, - expando: _stateObjectExpando, + expando: _GoRouteData.stateObjectExpando, ); return GoRoute( @@ -308,13 +306,6 @@ abstract class RelativeGoRouteData extends _GoRouteData { ); } - /// Used to cache [RelativeGoRouteData] that corresponds to a given [GoRouterState] - /// to minimize the number of times it has to be deserialized. - static final Expando<_GoRouteData> _stateObjectExpando = - Expando<_GoRouteData>( - 'GoRouteState to RelativeGoRouteData expando', - ); - /// The sub-location of this route, e.g. person/p1 String get subLocation => throw _GoRouteData.shouldBeGeneratedError; From 3bc0436a14b9691d3ddb86040de19b11d3d074e8 Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Thu, 14 Aug 2025 01:27:52 +0200 Subject: [PATCH 5/7] Remove unnecessary overrides --- packages/go_router/lib/src/route_data.dart | 106 +++++---------------- 1 file changed, 22 insertions(+), 84 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 2bf89d94f59..06d5a26abc8 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -25,19 +25,41 @@ abstract class _GoRouteData extends RouteData { const _GoRouteData(); /// Creates the [Widget] for `this` route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.builder]. Widget build(BuildContext context, GoRouterState state) => throw UnimplementedError( 'One of `build` or `buildPage` must be implemented.', ); /// A page builder for this route. + /// + /// Subclasses can override this function to provide a custom [Page]. + /// + /// Subclasses must override one of [build], [buildPage] or + /// [redirect]. + /// + /// Corresponds to [GoRoute.pageBuilder]. + /// + /// By default, returns a [Page] instance that is ignored, causing a default + /// [Page] implementation to be used with the results of [build]. Page buildPage(BuildContext context, GoRouterState state) => const NoOpPage(); /// An optional redirect function for this route. + /// + /// Subclasses must override one of [build], [buildPage], or + /// [redirect]. + /// + /// Corresponds to [GoRoute.redirect]. FutureOr redirect(BuildContext context, GoRouterState state) => null; /// Called when this route is removed from GoRouter's route history. + /// + /// Corresponds to [GoRoute.onExit]. FutureOr onExit(BuildContext context, GoRouterState state) => true; /// The error thrown when a user-facing method is not implemented by the @@ -118,48 +140,6 @@ abstract class GoRouteData extends _GoRouteData { /// [GoRouteData] is abstract and cannot be instantiated directly. const GoRouteData(); - /// Creates the [Widget] for `this` route. - /// - /// Subclasses must override one of [build], [buildPage], or - /// [redirect]. - /// - /// Corresponds to [GoRoute.builder]. - @override - Widget build(BuildContext context, GoRouterState state) => - super.build(context, state); - - /// A page builder for this route. - /// - /// Subclasses can override this function to provide a custom [Page]. - /// - /// Subclasses must override one of [build], [buildPage] or - /// [redirect]. - /// - /// Corresponds to [GoRoute.pageBuilder]. - /// - /// By default, returns a [Page] instance that is ignored, causing a default - /// [Page] implementation to be used with the results of [build]. - @override - Page buildPage(BuildContext context, GoRouterState state) => - super.buildPage(context, state); - - /// An optional redirect function for this route. - /// - /// Subclasses must override one of [build], [buildPage], or - /// [redirect]. - /// - /// Corresponds to [GoRoute.redirect]. - @override - FutureOr redirect(BuildContext context, GoRouterState state) => - super.redirect(context, state); - - /// Called when this route is removed from GoRouter's route history. - /// - /// Corresponds to [GoRoute.onExit]. - @override - FutureOr onExit(BuildContext context, GoRouterState state) => - super.onExit(context, state); - /// A helper function used by generated code. /// /// Should not be used directly. @@ -231,48 +211,6 @@ abstract class RelativeGoRouteData extends _GoRouteData { /// [RelativeGoRouteData] is abstract and cannot be instantiated directly. const RelativeGoRouteData(); - /// Creates the [Widget] for `this` route. - /// - /// Subclasses must override one of [build], [buildPage], or - /// [redirect]. - /// - /// Corresponds to [GoRoute.builder]. - @override - Widget build(BuildContext context, GoRouterState state) => - super.build(context, state); - - /// A page builder for this route. - /// - /// Subclasses can override this function to provide a custom [Page]. - /// - /// Subclasses must override one of [build], [buildPage] or - /// [redirect]. - /// - /// Corresponds to [GoRoute.pageBuilder]. - /// - /// By default, returns a [Page] instance that is ignored, causing a default - /// [Page] implementation to be used with the results of [build]. - @override - Page buildPage(BuildContext context, GoRouterState state) => - super.buildPage(context, state); - - /// An optional redirect function for this route. - /// - /// Subclasses must override one of [build], [buildPage], or - /// [redirect]. - /// - /// Corresponds to [GoRoute.redirect]. - @override - FutureOr redirect(BuildContext context, GoRouterState state) => - super.redirect(context, state); - - /// Called when this route is removed from GoRouter's route history. - /// - /// Corresponds to [GoRoute.onExit]. - @override - FutureOr onExit(BuildContext context, GoRouterState state) => - super.onExit(context, state); - /// A helper function used by generated code. /// /// Should not be used directly. From c74eb7a98557ce3238429c2c04bfae0d69ef9e3b Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Thu, 14 Aug 2025 18:28:02 +0200 Subject: [PATCH 6/7] Rename base class --- packages/go_router/lib/src/route_data.dart | 69 +++--- packages/go_router/test/route_data_test.dart | 235 +++++++++---------- 2 files changed, 152 insertions(+), 152 deletions(-) diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart index 06d5a26abc8..815f659eccc 100644 --- a/packages/go_router/lib/src/route_data.dart +++ b/packages/go_router/lib/src/route_data.dart @@ -21,8 +21,8 @@ abstract class RouteData { /// A base class for [GoRouteData] and [RelativeGoRouteData] that provides /// common functionality for type-safe routing. -abstract class _GoRouteData extends RouteData { - const _GoRouteData(); +abstract class _GoRouteDataBase extends RouteData { + const _GoRouteDataBase(); /// Creates the [Widget] for `this` route. /// @@ -65,13 +65,13 @@ abstract class _GoRouteData extends RouteData { /// The error thrown when a user-facing method is not implemented by the /// generated code. static UnimplementedError get shouldBeGeneratedError => UnimplementedError( - 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).', - ); + 'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).', + ); - /// Used to cache [_GoRouteData] that corresponds to a given [GoRouterState] + /// Used to cache [_GoRouteDataBase] that corresponds to a given [GoRouterState] /// to minimize the number of times it has to be deserialized. - static final Expando<_GoRouteData> stateObjectExpando = - Expando<_GoRouteData>('GoRouteState to _GoRouteData expando'); + static final Expando<_GoRouteDataBase> stateObjectExpando = + Expando<_GoRouteDataBase>('GoRouteState to _GoRouteDataBase expando'); } /// Helper to build a location string from a path and query parameters. @@ -100,9 +100,9 @@ class _GoRouteParameters { } /// Helper to create [GoRoute] parameters from a factory function and an Expando. -_GoRouteParameters _createGoRouteParameters({ +_GoRouteParameters _createGoRouteParameters({ required T Function(GoRouterState) factory, - required Expando<_GoRouteData> expando, + required Expando<_GoRouteDataBase> expando, }) { T factoryImpl(GoRouterState state) { final Object? extra = state.extra; @@ -117,14 +117,18 @@ _GoRouteParameters _createGoRouteParameters({ } return _GoRouteParameters( - builder: (BuildContext context, GoRouterState state) => - factoryImpl(state).build(context, state), - pageBuilder: (BuildContext context, GoRouterState state) => - factoryImpl(state).buildPage(context, state), - redirect: (BuildContext context, GoRouterState state) => - factoryImpl(state).redirect(context, state), - onExit: (BuildContext context, GoRouterState state) => - factoryImpl(state).onExit(context, state), + builder: + (BuildContext context, GoRouterState state) => + factoryImpl(state).build(context, state), + pageBuilder: + (BuildContext context, GoRouterState state) => + factoryImpl(state).buildPage(context, state), + redirect: + (BuildContext context, GoRouterState state) => + factoryImpl(state).redirect(context, state), + onExit: + (BuildContext context, GoRouterState state) => + factoryImpl(state).onExit(context, state), ); } @@ -134,7 +138,7 @@ _GoRouteParameters _createGoRouteParameters({ /// Subclasses must override one of [build], [buildPage], or /// [redirect]. /// {@category Type-safe routes} -abstract class GoRouteData extends _GoRouteData { +abstract class GoRouteData extends _GoRouteDataBase { /// Allows subclasses to have `const` constructors. /// /// [GoRouteData] is abstract and cannot be instantiated directly. @@ -159,7 +163,7 @@ abstract class GoRouteData extends _GoRouteData { }) { final _GoRouteParameters params = _createGoRouteParameters( factory: factory, - expando: _GoRouteData.stateObjectExpando, + expando: _GoRouteDataBase.stateObjectExpando, ); return GoRoute( @@ -176,18 +180,19 @@ abstract class GoRouteData extends _GoRouteData { } /// The location of this route, e.g. /family/f2/person/p1 - String get location => throw _GoRouteData.shouldBeGeneratedError; + String get location => throw _GoRouteDataBase.shouldBeGeneratedError; /// Navigate to the route. - void go(BuildContext context) => throw _GoRouteData.shouldBeGeneratedError; + void go(BuildContext context) => + throw _GoRouteDataBase.shouldBeGeneratedError; /// Push the route onto the page stack. Future push(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; /// Replaces the top-most page of the page stack with the route. void pushReplacement(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; /// Replaces the top-most page of the page stack with the route but treats /// it as the same page. @@ -196,7 +201,7 @@ abstract class GoRouteData extends _GoRouteData { /// page animation. /// void replace(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; } /// A class to represent a relative [GoRoute] in @@ -205,7 +210,7 @@ abstract class GoRouteData extends _GoRouteData { /// Subclasses must override one of [build], [buildPage], or /// [redirect]. /// {@category Type-safe routes} -abstract class RelativeGoRouteData extends _GoRouteData { +abstract class RelativeGoRouteData extends _GoRouteDataBase { /// Allows subclasses to have `const` constructors. /// /// [RelativeGoRouteData] is abstract and cannot be instantiated directly. @@ -229,7 +234,7 @@ abstract class RelativeGoRouteData extends _GoRouteData { }) { final _GoRouteParameters params = _createGoRouteParameters( factory: factory, - expando: _GoRouteData.stateObjectExpando, + expando: _GoRouteDataBase.stateObjectExpando, ); return GoRoute( @@ -245,22 +250,22 @@ abstract class RelativeGoRouteData extends _GoRouteData { } /// The sub-location of this route, e.g. person/p1 - String get subLocation => throw _GoRouteData.shouldBeGeneratedError; + String get subLocation => throw _GoRouteDataBase.shouldBeGeneratedError; /// The relative location of this route, e.g. ./person/p1 - String get relativeLocation => throw _GoRouteData.shouldBeGeneratedError; + String get relativeLocation => throw _GoRouteDataBase.shouldBeGeneratedError; /// Navigate to the route. void goRelative(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; /// Push the route onto the page stack. Future pushRelative(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; /// Replaces the top-most page of the page stack with the route. void pushReplacementRelative(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; /// Replaces the top-most page of the page stack with the route but treats /// it as the same page. @@ -269,7 +274,7 @@ abstract class RelativeGoRouteData extends _GoRouteData { /// page animation. /// void replaceRelative(BuildContext context) => - throw _GoRouteData.shouldBeGeneratedError; + throw _GoRouteDataBase.shouldBeGeneratedError; } /// A class to represent a [ShellRoute] in diff --git a/packages/go_router/test/route_data_test.dart b/packages/go_router/test/route_data_test.dart index 5bff78166cc..b14313c3b4a 100644 --- a/packages/go_router/test/route_data_test.dart +++ b/packages/go_router/test/route_data_test.dart @@ -93,19 +93,7 @@ class _RelativeGoRouteDataBuildPage extends RelativeGoRouteData { @override Page buildPage(BuildContext context, GoRouterState state) => - const MaterialPage( - child: SizedBox(key: Key('buildPage')), - ); -} - -class _RelativeGoRouteDataBuildPage extends RelativeGoRouteData { - const _RelativeGoRouteDataBuildPage(); - - @override - Page buildPage(BuildContext context, GoRouterState state) => - const MaterialPage( - child: SizedBox(key: Key('buildPage')), - ); + const MaterialPage(child: SizedBox(key: Key('buildPage'))); } class _ShellRouteDataPageBuilder extends ShellRouteData { @@ -325,73 +313,75 @@ void main() { }, ); - testWidgets( - 'It should throw because there is no code generated', - (WidgetTester tester) async { - final List errors = []; + testWidgets('It should throw because there is no code generated', ( + WidgetTester tester, + ) async { + final List errors = []; FlutterError.onError = (FlutterErrorDetails details) => errors.add(details); const String errorText = 'Should be generated'; - Future expectUnimplementedError( - void Function(BuildContext) onTap) async { - await tester.pumpWidget(MaterialApp( + Future expectUnimplementedError( + void Function(BuildContext) onTap, + ) async { + await tester.pumpWidget( + MaterialApp( home: Builder( - builder: (BuildContext context) => GestureDetector( - child: const Text('Tap'), - onTap: () => onTap(context), - ), + builder: + (BuildContext context) => GestureDetector( + child: const Text('Tap'), + onTap: () => onTap(context), + ), ), - )); - await tester.tap(find.text('Tap')); + ), + ); + await tester.tap(find.text('Tap')); - expect(errors.first.exception, isA()); - expect(errors.first.exception.toString(), contains(errorText)); + expect(errors.first.exception, isA()); + expect(errors.first.exception.toString(), contains(errorText)); - errors.clear(); - } + errors.clear(); + } - await expectUnimplementedError((BuildContext context) { - const _GoRouteDataBuild().location; - }); + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().location; + }); - await expectUnimplementedError((BuildContext context) { - const _GoRouteDataBuild().push(context); - }); + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().push(context); + }); - await expectUnimplementedError((BuildContext context) { - const _GoRouteDataBuild().go(context); - }); + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().go(context); + }); - await expectUnimplementedError((BuildContext context) { - const _GoRouteDataBuild().pushReplacement(context); - }); + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().pushReplacement(context); + }); - await expectUnimplementedError((BuildContext context) { - const _GoRouteDataBuild().replace(context); - }); + await expectUnimplementedError((BuildContext context) { + const _GoRouteDataBuild().replace(context); + }); - FlutterError.onError = FlutterError.dumpErrorToConsole; - }, - ); + FlutterError.onError = FlutterError.dumpErrorToConsole; + }); }); group('RelativeGoRouteData', () { - testWidgets( - 'It should build the page from the overridden build method', - (WidgetTester tester) async { - final GoRouter goRouter = GoRouter( - initialLocation: '/build', - routes: _relativeRoutes, - ); - addTearDown(goRouter.dispose); - await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); - expect(find.byKey(const Key('build')), findsOneWidget); - expect(find.byKey(const Key('buildPage')), findsNothing); - }, - ); + testWidgets('It should build the page from the overridden build method', ( + WidgetTester tester, + ) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/build', + routes: _relativeRoutes, + ); + addTearDown(goRouter.dispose); + await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); + expect(find.byKey(const Key('build')), findsOneWidget); + expect(find.byKey(const Key('buildPage')), findsNothing); + }); testWidgets( 'It should build the page from the overridden buildPage method', @@ -412,9 +402,10 @@ void main() { (WidgetTester tester) async { final GoRoute routeWithDefaultCaseSensitivity = RelativeGoRouteData.$route( - path: 'path', - factory: (GoRouterState state) => const _RelativeGoRouteDataBuild(), - ); + path: 'path', + factory: + (GoRouterState state) => const _RelativeGoRouteDataBuild(), + ); expect(routeWithDefaultCaseSensitivity.caseSensitive, true); }, @@ -425,66 +416,71 @@ void main() { (WidgetTester tester) async { final GoRoute routeWithDefaultCaseSensitivity = RelativeGoRouteData.$route( - path: 'path', - caseSensitive: false, - factory: (GoRouterState state) => const _RelativeGoRouteDataBuild(), - ); + path: 'path', + caseSensitive: false, + factory: + (GoRouterState state) => const _RelativeGoRouteDataBuild(), + ); expect(routeWithDefaultCaseSensitivity.caseSensitive, false); }, ); - testWidgets( - 'It should throw because there is no code generated', - (WidgetTester tester) async { - final List errors = []; + testWidgets('It should throw because there is no code generated', ( + WidgetTester tester, + ) async { + final List errors = []; - FlutterError.onError = - (FlutterErrorDetails details) => errors.add(details); + FlutterError.onError = + (FlutterErrorDetails details) => errors.add(details); - const String errorText = 'Should be generated'; + const String errorText = 'Should be generated'; - Future expectUnimplementedError( - void Function(BuildContext) onTap) async { - await tester.pumpWidget(MaterialApp( + Future expectUnimplementedError( + void Function(BuildContext) onTap, + ) async { + await tester.pumpWidget( + MaterialApp( home: Builder( - builder: (BuildContext context) => GestureDetector( - child: const Text('Tap'), - onTap: () => onTap(context), - ), + builder: + (BuildContext context) => GestureDetector( + child: const Text('Tap'), + onTap: () => onTap(context), + ), ), - )); - await tester.tap(find.text('Tap')); + ), + ); + await tester.tap(find.text('Tap')); - expect(errors.first.exception, isA()); - expect(errors.first.exception.toString(), contains(errorText)); + expect(errors.first.exception, isA()); + expect(errors.first.exception.toString(), contains(errorText)); - errors.clear(); - } + errors.clear(); + } - await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().subLocation; - }); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().subLocation; + }); - await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().relativeLocation; - }); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().relativeLocation; + }); - await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().pushRelative(context); - }); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().pushRelative(context); + }); - await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().goRelative(context); - }); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().goRelative(context); + }); - await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().pushReplacementRelative(context); - }); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().pushReplacementRelative(context); + }); - await expectUnimplementedError((BuildContext context) { - const _RelativeGoRouteDataBuild().replaceRelative(context); - }); + await expectUnimplementedError((BuildContext context) { + const _RelativeGoRouteDataBuild().replaceRelative(context); + }); FlutterError.onError = FlutterError.dumpErrorToConsole; }); @@ -774,9 +770,7 @@ void main() { test('TypedRelativeGoRoute with default parameters', () { const TypedRelativeGoRoute typedGoRoute = - TypedRelativeGoRoute( - path: 'path', - ); + TypedRelativeGoRoute(path: 'path'); expect(typedGoRoute.path, 'path'); expect(typedGoRoute.caseSensitive, true); @@ -786,15 +780,15 @@ void main() { test('TypedRelativeGoRoute with provided parameters', () { const TypedRelativeGoRoute typedGoRoute = TypedRelativeGoRoute( - path: 'path', - caseSensitive: false, - routes: >[ - TypedRelativeGoRoute( - path: 'sub-path', + path: 'path', caseSensitive: false, - ), - ], - ); + routes: >[ + TypedRelativeGoRoute( + path: 'sub-path', + caseSensitive: false, + ), + ], + ); expect(typedGoRoute.path, 'path'); expect(typedGoRoute.caseSensitive, false); @@ -803,9 +797,10 @@ void main() { typedGoRoute.routes.single, isA>() .having( - (TypedRelativeGoRoute route) => route.path, - 'path', - 'sub-path') + (TypedRelativeGoRoute route) => route.path, + 'path', + 'sub-path', + ) .having( (TypedRelativeGoRoute route) => route.caseSensitive, From 56ddbea1cd2cf62311d7b504cac114ea0c0487da Mon Sep 17 00:00:00 2001 From: LukasMirbt Date: Mon, 18 Aug 2025 22:29:56 +0200 Subject: [PATCH 7/7] Bump minor version --- packages/go_router/CHANGELOG.md | 2 +- packages/go_router/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 089de3e9efe..f3c115ff348 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,4 +1,4 @@ -## 16.1.1 +## 16.2.0 - Adds `RelativeGoRouteData` and `TypedRelativeGoRoute`. - Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index f163f276e4c..ff0f253797f 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,7 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 16.1.1 +version: 16.2.0 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22