blob: ad2573f61414e2d4f110e83fdc82cee151fafacf [file] [log] [blame]
// Copyright 2013 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';
import 'package:go_router/go_router.dart';
import 'package:go_router/src/configuration.dart';
import 'package:go_router/src/information_provider.dart';
import 'package:go_router/src/match.dart';
import 'package:go_router/src/parser.dart';
RouteInformation createRouteInformation(String location, [Object? extra]) {
return RouteInformation(
// TODO(chunhtai): remove this ignore and migrate the code
// https://github.com/flutter/flutter/issues/124045.
// ignore: deprecated_member_use
location: location,
state:
RouteInformationState<void>(type: NavigatingType.go, extra: extra));
}
void main() {
Future<GoRouteInformationParser> createParser(
WidgetTester tester, {
required List<RouteBase> routes,
int redirectLimit = 5,
GoRouterRedirect? redirect,
}) async {
final GoRouter router = GoRouter(
routes: routes,
redirectLimit: redirectLimit,
redirect: redirect,
);
await tester.pumpWidget(MaterialApp.router(
routerConfig: router,
));
return router.routeInformationParser;
}
testWidgets('GoRouteInformationParser can parse route',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/'), context);
List<RouteMatch> matches = matchesObj.matches;
expect(matches.length, 1);
expect(matchesObj.uri.toString(), '/');
expect(matchesObj.extra, isNull);
expect(matches[0].matchedLocation, '/');
expect(matches[0].route, routes[0]);
final Object extra = Object();
matchesObj = await parser.parseRouteInformationWithDependencies(
createRouteInformation('/abc?def=ghi', extra), context);
matches = matchesObj.matches;
expect(matches.length, 2);
expect(matchesObj.uri.toString(), '/abc?def=ghi');
expect(matchesObj.extra, extra);
expect(matches[0].matchedLocation, '/');
expect(matches[0].route, routes[0]);
expect(matches[1].matchedLocation, '/abc');
expect(matches[1].route, routes[0].routes[0]);
});
test('GoRouteInformationParser can retrieve route by name', () async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
name: 'lowercase',
builder: (_, __) => const Placeholder(),
),
GoRoute(
path: 'efg',
name: 'camelCase',
builder: (_, __) => const Placeholder(),
),
GoRoute(
path: 'hij',
name: 'snake_case',
builder: (_, __) => const Placeholder(),
),
],
),
];
final RouteConfiguration configuration = RouteConfiguration(
routes: routes,
redirectLimit: 100,
topRedirect: (_, __) => null,
navigatorKey: GlobalKey<NavigatorState>(),
);
expect(configuration.namedLocation('lowercase'), '/abc');
expect(configuration.namedLocation('camelCase'), '/efg');
expect(configuration.namedLocation('snake_case'), '/hij');
// With query parameters
expect(configuration.namedLocation('lowercase'), '/abc');
expect(
configuration.namedLocation('lowercase',
queryParameters: const <String, String>{'q': '1'}),
'/abc?q=1');
expect(
configuration.namedLocation('lowercase',
queryParameters: const <String, String>{'q': '1', 'g': '2'}),
'/abc?q=1&g=2');
});
test(
'GoRouteInformationParser can retrieve route by name with query parameters',
() async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
name: 'routeName',
builder: (_, __) => const Placeholder(),
),
],
),
];
final RouteConfiguration configuration = RouteConfiguration(
routes: routes,
redirectLimit: 100,
topRedirect: (_, __) => null,
navigatorKey: GlobalKey<NavigatorState>(),
);
expect(
configuration
.namedLocation('routeName', queryParameters: const <String, dynamic>{
'q1': 'v1',
'q2': <String>['v2', 'v3'],
}),
'/abc?q1=v1&q2=v2&q2=v3',
);
});
testWidgets('GoRouteInformationParser returns error when unknown route',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/def'), context);
final List<RouteMatch> matches = matchesObj.matches;
expect(matches.length, 0);
expect(matchesObj.uri.toString(), '/def');
expect(matchesObj.extra, isNull);
expect(matchesObj.error!.toString(),
'GoException: no routes for location: /def');
});
testWidgets(
'GoRouteInformationParser calls redirector with correct uri when unknown route',
(WidgetTester tester) async {
String? lastRedirectLocation;
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: 'abc',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, GoRouterState state) {
lastRedirectLocation = state.location;
return null;
},
);
final BuildContext context = tester.element(find.byType(Router<Object>));
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/def'), context);
expect(lastRedirectLocation, '/def');
});
testWidgets('GoRouteInformationParser can work with route parameters',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: ':uid/family/:fid',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/123/family/456'), context);
final List<RouteMatch> matches = matchesObj.matches;
expect(matches.length, 2);
expect(matchesObj.uri.toString(), '/123/family/456');
expect(matchesObj.pathParameters.length, 2);
expect(matchesObj.pathParameters['uid'], '123');
expect(matchesObj.pathParameters['fid'], '456');
expect(matchesObj.extra, isNull);
expect(matches[0].matchedLocation, '/');
expect(matches[1].matchedLocation, '/123/family/456');
});
testWidgets(
'GoRouteInformationParser processes top level redirect when there is no match',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: ':uid/family/:fid',
builder: (_, __) => const Placeholder(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (BuildContext context, GoRouterState state) {
if (state.location != '/123/family/345') {
return '/123/family/345';
}
return null;
},
);
final BuildContext context = tester.element(find.byType(Router<Object>));
final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/random/uri'), context);
final List<RouteMatch> matches = matchesObj.matches;
expect(matches.length, 2);
expect(matchesObj.uri.toString(), '/123/family/345');
expect(matches[0].matchedLocation, '/');
expect(matches[1].matchedLocation, '/123/family/345');
});
testWidgets(
'GoRouteInformationParser can do route level redirect when there is a match',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (_, __) => const Placeholder(),
routes: <GoRoute>[
GoRoute(
path: ':uid/family/:fid',
builder: (_, __) => const Placeholder(),
),
GoRoute(
path: 'redirect',
redirect: (_, __) => '/123/family/345',
builder: (_, __) => throw UnimplementedError(),
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/redirect'), context);
final List<RouteMatch> matches = matchesObj.matches;
expect(matches.length, 2);
expect(matchesObj.uri.toString(), '/123/family/345');
expect(matches[0].matchedLocation, '/');
expect(matches[1].matchedLocation, '/123/family/345');
});
testWidgets(
'GoRouteInformationParser throws an exception when route is malformed',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/abc',
builder: (_, __) => const Placeholder(),
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirectLimit: 100,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
expect(() async {
await parser.parseRouteInformationWithDependencies(
createRouteInformation('::Not valid URI::'), context);
}, throwsA(isA<FormatException>()));
});
testWidgets(
'GoRouteInformationParser returns an error if a redirect is detected.',
(WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/abc',
builder: (_, __) => const Placeholder(),
redirect: (BuildContext context, GoRouterState state) => state.location,
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/abd'), context);
final List<RouteMatch> matches = matchesObj.matches;
expect(matches, hasLength(0));
expect(matchesObj.error, isNotNull);
});
testWidgets('Creates a match for ShellRoute', (WidgetTester tester) async {
final List<RouteBase> routes = <RouteBase>[
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return Scaffold(
body: child,
);
},
routes: <RouteBase>[
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) {
return const Scaffold(
body: Text('Screen A'),
);
},
),
GoRoute(
path: '/b',
builder: (BuildContext context, GoRouterState state) {
return const Scaffold(
body: Text('Screen B'),
);
},
),
],
),
];
final GoRouteInformationParser parser = await createParser(
tester,
routes: routes,
redirect: (_, __) => null,
);
final BuildContext context = tester.element(find.byType(Router<Object>));
final RouteMatchList matchesObj =
await parser.parseRouteInformationWithDependencies(
createRouteInformation('/a'), context);
final List<RouteMatch> matches = matchesObj.matches;
expect(matches, hasLength(2));
expect(matchesObj.error, isNull);
});
}