Unverified Commit 89d1b9e4 authored by chunhtai's avatar chunhtai Committed by GitHub

Makes PlatformInformationProvider aware of the browser default route … (#88122)

parent 7864e64d
...@@ -201,39 +201,36 @@ class RouteInformation { ...@@ -201,39 +201,36 @@ class RouteInformation {
/// method. /// method.
/// ///
/// If the location in the new route information is different from the /// If the location in the new route information is different from the
/// current location, this is considered to be a navigation event, the router /// current location, this is considered to be a navigation event, the
/// sends the new route information to the [routeInformationProvider]'s /// [PlatformRouteInformationProvider.routerReportsNewRouteInformation] method
/// [RouteInformationProvider.routerReportsNewRouteInformation] method with /// calls [SystemNavigator.routeInformationUpdated] with `replace = false` to
/// `isNavigation` equals to true. That method as implemented in /// notify the engine, and through that the browser, to create a history entry
/// [PlatformRouteInformationProvider] uses /// with the new url. Otherwise,
/// [SystemNavigator.routeInformationUpdated] to notify the engine, and through /// [PlatformRouteInformationProvider.routerReportsNewRouteInformation] calls
/// that the browser, to create a history entry with the new url if the /// [SystemNavigator.routeInformationUpdated] with `replace = true` to update
/// `isNavigation` is true. /// the current history entry with the latest [RouteInformation].
///
/// If the location is the same as the current location but different state,
/// the router still sends the new route information to the
/// [routeInformationProvider]'s
/// [RouteInformationProvider.routerReportsNewRouteInformation] but with
/// `isNavigation` equals to false. This causes
/// [PlatformRouteInformationProvider] replace current history entry instead
/// of creating a new one.
/// ///
/// One can force the [Router] to report new route information as navigation /// One can force the [Router] to report new route information as navigation
/// event to the [routeInformationProvider] (and thus the browser) even if the /// event to the [routeInformationProvider] (and thus the browser) even if the
/// [RouteInformation.location] has not changed by calling the [Router.navigate] /// [RouteInformation.location] has not changed by calling the [Router.navigate]
/// method with a callback that performs the state change. This allows one to /// method with a callback that performs the state change. This causes [Router]
/// support the browser's back and forward buttons without changing the URL. For /// to call the [RouteInformationProvider.routerReportsNewRouteInformation] with
/// example, the scroll position of a scroll view may be saved in the /// [RouteInformationReportingType.navigate], and thus causes
/// [RouteInformation.state]. Using [Router.navigate] to update the scroll /// [PlatformRouteInformationProvider] to push a new history entry regardlessly.
/// position causes the browser to create a new history entry with the /// This allows one to support the browser's back and forward buttons without
/// changing the URL. For example, the scroll position of a scroll view may be
/// saved in the [RouteInformation.state]. Using [Router.navigate] to update the
/// scroll position causes the browser to create a new history entry with the
/// [RouteInformation.state] that stores this new scroll position. When the user /// [RouteInformation.state] that stores this new scroll position. When the user
/// clicks the back button, the app will go back to the previous scroll position /// clicks the back button, the app will go back to the previous scroll position
/// without changing the URL in the location bar. /// without changing the URL in the location bar.
/// ///
/// One can also force the [Router] to ignore a navigation event by making /// One can also force the [Router] to ignore a navigation event by making
/// those changes during a callback passed to [Router.neglect]. The [Router] /// those changes during a callback passed to [Router.neglect]. The [Router]
/// will not report the route information with `isNavigation` equals to false /// calls the [RouteInformationProvider.routerReportsNewRouteInformation] with
/// even if it detects location change as the result of running the callback. /// [RouteInformationReportingType.neglect], and thus causes
/// [PlatformRouteInformationProvider] to replace the current history entry
/// regardlessly even if it detects location change.
/// ///
/// To opt out of URL updates entirely, pass null for [routeInformationProvider] /// To opt out of URL updates entirely, pass null for [routeInformationProvider]
/// and [routeInformationParser]. This is not recommended in general, but may be /// and [routeInformationParser]. This is not recommended in general, but may be
...@@ -432,7 +429,7 @@ class Router<T> extends StatefulWidget { ...@@ -432,7 +429,7 @@ class Router<T> extends StatefulWidget {
final _RouterScope scope = context final _RouterScope scope = context
.getElementForInheritedWidgetOfExactType<_RouterScope>()! .getElementForInheritedWidgetOfExactType<_RouterScope>()!
.widget as _RouterScope; .widget as _RouterScope;
scope.routerState._setStateWithExplicitReportStatus(_IntentionToReportRouteInformation.must, callback); scope.routerState._setStateWithExplicitReportStatus(RouteInformationReportingType.navigate, callback);
} }
/// Forces the [Router] to run the [callback] without creating a new history /// Forces the [Router] to run the [callback] without creating a new history
...@@ -461,7 +458,7 @@ class Router<T> extends StatefulWidget { ...@@ -461,7 +458,7 @@ class Router<T> extends StatefulWidget {
final _RouterScope scope = context final _RouterScope scope = context
.getElementForInheritedWidgetOfExactType<_RouterScope>()! .getElementForInheritedWidgetOfExactType<_RouterScope>()!
.widget as _RouterScope; .widget as _RouterScope;
scope.routerState._setStateWithExplicitReportStatus(_IntentionToReportRouteInformation.ignore, callback); scope.routerState._setStateWithExplicitReportStatus(RouteInformationReportingType.neglect, callback);
} }
@override @override
...@@ -471,22 +468,33 @@ class Router<T> extends StatefulWidget { ...@@ -471,22 +468,33 @@ class Router<T> extends StatefulWidget {
typedef _AsyncPassthrough<Q> = Future<Q> Function(Q); typedef _AsyncPassthrough<Q> = Future<Q> Function(Q);
typedef _DelegateRouteSetter<T> = Future<void> Function(T); typedef _DelegateRouteSetter<T> = Future<void> Function(T);
// Whether to report the route information in this build cycle. /// The [Router]'s intention when it reports a new [RouteInformation] to the
enum _IntentionToReportRouteInformation { /// [RouteInformationProvider].
// We haven't receive any signal on whether to report. ///
/// See also:
///
/// * [RouteInformationProvider.routerReportsNewRouteInformation]: which is
/// called by the router when it has a new route information to report.
enum RouteInformationReportingType {
/// Router does not have a specific intention.
///
/// The router generates a new route information every time it detects route
/// information may have change due to a rebuild. This is the default type if
/// neither [Router.neglect] nor [Router.navigate] was used during the
/// rebuild.
none, none,
// Report if route information changes. /// The accompanying [RouteInformation] were generated during a
maybe, /// [Router.neglect] call.
// Report regardless of route information changes. neglect,
must, /// The accompanying [RouteInformation] were generated during a
// Don't report regardless of route information changes. /// [Router.navigate] call.
ignore, navigate,
} }
class _RouterState<T> extends State<Router<T>> with RestorationMixin { class _RouterState<T> extends State<Router<T>> with RestorationMixin {
Object? _currentRouteInformationParserTransaction; Object? _currentRouteInformationParserTransaction;
Object? _currentRouterDelegateTransaction; Object? _currentRouterDelegateTransaction;
_IntentionToReportRouteInformation _currentIntentionToReport = _IntentionToReportRouteInformation.none; RouteInformationReportingType? _currentIntentionToReport;
final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation(); final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation();
@override @override
...@@ -515,7 +523,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin { ...@@ -515,7 +523,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
void _scheduleRouteInformationReportingTask() { void _scheduleRouteInformationReportingTask() {
if (_routeInformationReportingTaskScheduled || widget.routeInformationProvider == null) if (_routeInformationReportingTaskScheduled || widget.routeInformationProvider == null)
return; return;
assert(_currentIntentionToReport != _IntentionToReportRouteInformation.none); assert(_currentIntentionToReport != null);
_routeInformationReportingTaskScheduled = true; _routeInformationReportingTaskScheduled = true;
SchedulerBinding.instance!.addPostFrameCallback(_reportRouteInformation); SchedulerBinding.instance!.addPostFrameCallback(_reportRouteInformation);
} }
...@@ -525,31 +533,11 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin { ...@@ -525,31 +533,11 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
_routeInformationReportingTaskScheduled = false; _routeInformationReportingTaskScheduled = false;
if (_routeInformation.value != null) { if (_routeInformation.value != null) {
final RouteInformation oldRouteInformation = widget.routeInformationProvider!.value;
final RouteInformation currentRouteInformation = _routeInformation.value!; final RouteInformation currentRouteInformation = _routeInformation.value!;
switch (_currentIntentionToReport) { assert(_currentIntentionToReport != null);
case _IntentionToReportRouteInformation.none: widget.routeInformationProvider!.routerReportsNewRouteInformation(currentRouteInformation, type: _currentIntentionToReport!);
assert(false, '_reportRouteInformation must not be called with _IntentionToReportRouteInformation.none');
return;
case _IntentionToReportRouteInformation.ignore:
if (oldRouteInformation.location != currentRouteInformation.location ||
oldRouteInformation.state != currentRouteInformation.state) {
widget.routeInformationProvider!.routerReportsNewRouteInformation(currentRouteInformation, isNavigation: false);
} }
break; _currentIntentionToReport = RouteInformationReportingType.none;
case _IntentionToReportRouteInformation.maybe:
if (oldRouteInformation.location != currentRouteInformation.location) {
widget.routeInformationProvider!.routerReportsNewRouteInformation(currentRouteInformation);
} else if (oldRouteInformation.state != currentRouteInformation.state) {
widget.routeInformationProvider!.routerReportsNewRouteInformation(currentRouteInformation, isNavigation: false);
}
break;
case _IntentionToReportRouteInformation.must:
widget.routeInformationProvider!.routerReportsNewRouteInformation(currentRouteInformation);
break;
}
}
_currentIntentionToReport = _IntentionToReportRouteInformation.none;
} }
RouteInformation? _retrieveNewRouteInformation() { RouteInformation? _retrieveNewRouteInformation() {
...@@ -560,13 +548,14 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin { ...@@ -560,13 +548,14 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
} }
void _setStateWithExplicitReportStatus( void _setStateWithExplicitReportStatus(
_IntentionToReportRouteInformation status, RouteInformationReportingType status,
VoidCallback fn, VoidCallback fn,
) { ) {
assert(status != null); assert(status != null);
assert(status.index >= _IntentionToReportRouteInformation.must.index); assert(status.index >= RouteInformationReportingType.neglect.index);
assert(() { assert(() {
if (_currentIntentionToReport.index >= _IntentionToReportRouteInformation.must.index && if (_currentIntentionToReport != null &&
_currentIntentionToReport != RouteInformationReportingType.none &&
_currentIntentionToReport != status) { _currentIntentionToReport != status) {
FlutterError.reportError( FlutterError.reportError(
const FlutterErrorDetails( const FlutterErrorDetails(
...@@ -587,9 +576,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin { ...@@ -587,9 +576,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
void _maybeNeedToReportRouteInformation() { void _maybeNeedToReportRouteInformation() {
_routeInformation.value = _retrieveNewRouteInformation(); _routeInformation.value = _retrieveNewRouteInformation();
_currentIntentionToReport = _currentIntentionToReport != _IntentionToReportRouteInformation.none _currentIntentionToReport ??= RouteInformationReportingType.none;
? _currentIntentionToReport
: _IntentionToReportRouteInformation.maybe;
_scheduleRouteInformationReportingTask(); _scheduleRouteInformationReportingTask();
} }
...@@ -1325,19 +1312,21 @@ abstract class RouteInformationProvider extends ValueListenable<RouteInformation ...@@ -1325,19 +1312,21 @@ abstract class RouteInformationProvider extends ValueListenable<RouteInformation
/// other side effects. For example, the [PlatformRouteInformationProvider] /// other side effects. For example, the [PlatformRouteInformationProvider]
/// overrides this method to report the route information back to the engine. /// overrides this method to report the route information back to the engine.
/// ///
/// The [routeInformation] is the new route information after the navigation /// The `routeInformation` is the new route information generated by the
/// event. /// Router rebuild, and it can be the same or different from the
/// [value].
/// ///
/// The [isNavigation] denotes whether the new route information is generated /// The `type` denotes the [Router]'s intention when it reports this
/// as a result of a navigation event. This information can be useful in a /// `routeInformation`. It is useful when deciding how to update the internal
/// web application, for example, the [PlatformRouteInformationProvider] uses /// state of [RouteInformationProvider] subclass with the `routeInformation`.
/// this flag to decide whether to create a browser history entry that enables /// For example, [PlatformRouteInformationProvider] uses this property to
/// browser backward and forward buttons. /// decide whether to push or replace the browser history entry with the new
/// `routeInformation`.
/// ///
/// For more information on how [Router] determines a navigation event, see /// For more information on how [Router] determines a navigation event, see
/// the "URL updates for web applications" section in the [Router] /// the "URL updates for web applications" section in the [Router]
/// documentation. /// documentation.
void routerReportsNewRouteInformation(RouteInformation routeInformation, {bool isNavigation = true}) {} void routerReportsNewRouteInformation(RouteInformation routeInformation, {required RouteInformationReportingType type}) {}
} }
/// The route information provider that propagates the platform route information changes. /// The route information provider that propagates the platform route information changes.
...@@ -1360,24 +1349,32 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid ...@@ -1360,24 +1349,32 @@ class PlatformRouteInformationProvider extends RouteInformationProvider with Wid
}) : _value = initialRouteInformation; }) : _value = initialRouteInformation;
@override @override
void routerReportsNewRouteInformation(RouteInformation routeInformation, {bool isNavigation = true}) { void routerReportsNewRouteInformation(RouteInformation routeInformation, {required RouteInformationReportingType type}) {
final bool replace =
type == RouteInformationReportingType.neglect ||
(type == RouteInformationReportingType.none &&
_valueInEngine.location == routeInformation.location);
SystemNavigator.selectMultiEntryHistory(); SystemNavigator.selectMultiEntryHistory();
SystemNavigator.routeInformationUpdated( SystemNavigator.routeInformationUpdated(
location: routeInformation.location!, location: routeInformation.location!,
state: routeInformation.state, state: routeInformation.state,
replace: !isNavigation, replace: replace,
); );
_value = routeInformation; _value = routeInformation;
_valueInEngine = routeInformation;
} }
@override @override
RouteInformation get value => _value; RouteInformation get value => _value;
RouteInformation _value; RouteInformation _value;
RouteInformation _valueInEngine = RouteInformation(location: WidgetsBinding.instance!.window.defaultRouteName);
void _platformReportsNewRouteInformation(RouteInformation routeInformation) { void _platformReportsNewRouteInformation(RouteInformation routeInformation) {
if (_value == routeInformation) if (_value == routeInformation)
return; return;
_value = routeInformation; _value = routeInformation;
_valueInEngine = routeInformation;
notifyListeners(); notifyListeners();
} }
......
...@@ -286,6 +286,15 @@ void main() { ...@@ -286,6 +286,15 @@ void main() {
routerDelegate: delegate, routerDelegate: delegate,
)); ));
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
'location': 'initial',
'state': null,
'replace': false,
}),
]);
log.clear();
// Triggers a router rebuild and verify the route information is reported // Triggers a router rebuild and verify the route information is reported
// to the web engine. // to the web engine.
......
...@@ -477,14 +477,14 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -477,14 +477,14 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
testWidgets('router does report URL change correctly', (WidgetTester tester) async { testWidgets('router does report URL change correctly', (WidgetTester tester) async {
RouteInformation? reportedRouteInformation; RouteInformation? reportedRouteInformation;
bool? reportedIsNavigation; RouteInformationReportingType? reportedType;
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
onRouterReport: (RouteInformation information, bool isNavigation) { onRouterReport: (RouteInformation information, RouteInformationReportingType type) {
// Makes sure we only report once after manually cleaning up. // Makes sure we only report once after manually cleaning up.
expect(reportedRouteInformation, isNull); expect(reportedRouteInformation, isNull);
expect(reportedIsNavigation, isNull); expect(reportedType, isNull);
reportedRouteInformation = information; reportedRouteInformation = information;
reportedIsNavigation = isNavigation; reportedType = type;
}, },
); );
final SimpleRouterDelegate delegate = SimpleRouterDelegate( final SimpleRouterDelegate delegate = SimpleRouterDelegate(
...@@ -514,7 +514,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -514,7 +514,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
), ),
)); ));
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
expect(reportedRouteInformation, isNull); expect(reportedRouteInformation!.location, 'initial');
expect(reportedType, RouteInformationReportingType.none);
reportedRouteInformation = null;
reportedType = null;
delegate.routeInformation = const RouteInformation( delegate.routeInformation = const RouteInformation(
location: 'update', location: 'update',
); );
...@@ -522,11 +525,11 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -522,11 +525,11 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
expect(find.text('update'), findsOneWidget); expect(find.text('update'), findsOneWidget);
expect(reportedRouteInformation!.location, 'update'); expect(reportedRouteInformation!.location, 'update');
expect(reportedIsNavigation, isTrue); expect(reportedType, RouteInformationReportingType.none);
// The router should report as non navigation event if only state changes. // The router should report as non navigation event if only state changes.
reportedRouteInformation = null; reportedRouteInformation = null;
reportedIsNavigation = null; reportedType = null;
delegate.routeInformation = const RouteInformation( delegate.routeInformation = const RouteInformation(
location: 'update', location: 'update',
state: 'another state', state: 'another state',
...@@ -535,31 +538,31 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -535,31 +538,31 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
expect(find.text('update'), findsOneWidget); expect(find.text('update'), findsOneWidget);
expect(reportedRouteInformation!.location, 'update'); expect(reportedRouteInformation!.location, 'update');
expect(reportedRouteInformation!.state, 'another state'); expect(reportedRouteInformation!.state, 'another state');
expect(reportedIsNavigation, isFalse); expect(reportedType, RouteInformationReportingType.none);
reportedRouteInformation = null; reportedRouteInformation = null;
reportedIsNavigation = null; reportedType = null;
bool result = false; bool result = false;
result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false)); result = await outerDispatcher.invokeCallback(SynchronousFuture<bool>(false));
expect(result, isTrue); expect(result, isTrue);
await tester.pump(); await tester.pump();
expect(find.text('popped'), findsOneWidget); expect(find.text('popped'), findsOneWidget);
expect(reportedRouteInformation!.location, 'popped'); expect(reportedRouteInformation!.location, 'popped');
expect(reportedIsNavigation, isTrue); expect(reportedType, RouteInformationReportingType.none);
}); });
testWidgets('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async { testWidgets('router can be forced to recognize or ignore navigating events', (WidgetTester tester) async {
RouteInformation? reportedRouteInformation; RouteInformation? reportedRouteInformation;
bool? reportedIsNavigation; RouteInformationReportingType? reportedType;
bool isNavigating = false; bool isNavigating = false;
late RouteInformation nextRouteInformation; late RouteInformation nextRouteInformation;
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
onRouterReport: (RouteInformation information, bool isNavigation) { onRouterReport: (RouteInformation information, RouteInformationReportingType type) {
// Makes sure we only report once after manually cleaning up. // Makes sure we only report once after manually cleaning up.
expect(reportedRouteInformation, isNull); expect(reportedRouteInformation, isNull);
expect(reportedIsNavigation, isNull); expect(reportedType, isNull);
reportedRouteInformation = information; reportedRouteInformation = information;
reportedIsNavigation = isNavigation; reportedType = type;
}, },
); );
provider.value = const RouteInformation( provider.value = const RouteInformation(
...@@ -595,7 +598,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -595,7 +598,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
), ),
)); ));
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
expect(reportedRouteInformation, isNull); expect(reportedRouteInformation!.location, 'initial');
expect(reportedType, RouteInformationReportingType.none);
reportedType = null;
reportedRouteInformation = null;
nextRouteInformation = const RouteInformation( nextRouteInformation = const RouteInformation(
location: 'update', location: 'update',
...@@ -604,9 +610,9 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -604,9 +610,9 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
await tester.pump(); await tester.pump();
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
expect(find.text('update'), findsOneWidget); expect(find.text('update'), findsOneWidget);
expect(reportedIsNavigation, isFalse); expect(reportedType, RouteInformationReportingType.neglect);
expect(reportedRouteInformation!.location, 'update'); expect(reportedRouteInformation!.location, 'update');
reportedIsNavigation = null; reportedType = null;
reportedRouteInformation = null; reportedRouteInformation = null;
isNavigating = true; isNavigating = true;
...@@ -615,21 +621,22 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -615,21 +621,22 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
// report a route information because isNavigating = true. // report a route information because isNavigating = true.
await tester.tap(find.byType(ElevatedButton)); await tester.tap(find.byType(ElevatedButton));
await tester.pump(); await tester.pump();
expect(reportedIsNavigation, isTrue); expect(reportedType, RouteInformationReportingType.navigate);
expect(reportedRouteInformation!.location, 'update'); expect(reportedRouteInformation!.location, 'update');
reportedIsNavigation = null; reportedType = null;
reportedRouteInformation = null; reportedRouteInformation = null;
}); });
testWidgets('router ignore navigating events updates RouteInformationProvider', (WidgetTester tester) async { testWidgets('router ignore navigating events updates RouteInformationProvider', (WidgetTester tester) async {
RouteInformation? updatedRouteInformation; RouteInformation? updatedRouteInformation;
late RouteInformation nextRouteInformation; late RouteInformation nextRouteInformation;
RouteInformationReportingType? reportingType;
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
onRouterReport: (RouteInformation information, bool isNavigation) { onRouterReport: (RouteInformation information, RouteInformationReportingType type) {
// This should never be a navigation event. expect(reportingType, isNull);
expect(isNavigation, false);
expect(updatedRouteInformation, isNull); expect(updatedRouteInformation, isNull);
updatedRouteInformation = information; updatedRouteInformation = information;
reportingType = type;
}, },
); );
provider.value = const RouteInformation( provider.value = const RouteInformation(
...@@ -658,7 +665,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -658,7 +665,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
), ),
)); ));
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
expect(updatedRouteInformation, isNull); expect(updatedRouteInformation!.location, 'initial');
expect(reportingType, RouteInformationReportingType.none);
updatedRouteInformation = null;
reportingType = null;
nextRouteInformation = const RouteInformation( nextRouteInformation = const RouteInformation(
location: 'update', location: 'update',
...@@ -668,17 +678,20 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -668,17 +678,20 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
expect(find.text('update'), findsOneWidget); expect(find.text('update'), findsOneWidget);
expect(updatedRouteInformation!.location, 'update'); expect(updatedRouteInformation!.location, 'update');
expect(reportingType, RouteInformationReportingType.neglect);
}); });
testWidgets('state change without location changes updates RouteInformationProvider', (WidgetTester tester) async { testWidgets('state change without location changes updates RouteInformationProvider', (WidgetTester tester) async {
RouteInformation? updatedRouteInformation; RouteInformation? updatedRouteInformation;
late RouteInformation nextRouteInformation; late RouteInformation nextRouteInformation;
RouteInformationReportingType? reportingType;
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
onRouterReport: (RouteInformation information, bool isNavigation) { onRouterReport: (RouteInformation information, RouteInformationReportingType type) {
// This should never be a navigation event. // This should never be a navigation event.
expect(isNavigation, false); expect(reportingType, isNull);
expect(updatedRouteInformation, isNull); expect(updatedRouteInformation, isNull);
updatedRouteInformation = information; updatedRouteInformation = information;
reportingType = type;
}, },
); );
provider.value = const RouteInformation( provider.value = const RouteInformation(
...@@ -705,7 +718,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -705,7 +718,10 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
), ),
)); ));
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
expect(updatedRouteInformation, isNull); expect(updatedRouteInformation!.location, 'initial');
expect(reportingType, RouteInformationReportingType.none);
updatedRouteInformation = null;
reportingType = null;
nextRouteInformation = const RouteInformation( nextRouteInformation = const RouteInformation(
location: 'initial', location: 'initial',
...@@ -715,45 +731,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -715,45 +731,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
await tester.pump(); await tester.pump();
expect(updatedRouteInformation!.location, 'initial'); expect(updatedRouteInformation!.location, 'initial');
expect(updatedRouteInformation!.state, 'state2'); expect(updatedRouteInformation!.state, 'state2');
}); expect(reportingType, RouteInformationReportingType.none);
testWidgets('router does not report when route information is up to date with route information provider', (WidgetTester tester) async {
RouteInformation? reportedRouteInformation;
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
onRouterReport: (RouteInformation information, bool isNavigation) {
reportedRouteInformation = information;
},
);
provider.value = const RouteInformation(
location: 'initial',
);
final SimpleRouterDelegate delegate = SimpleRouterDelegate(reportConfiguration: true);
delegate.builder = (BuildContext context, RouteInformation? routeInformation) {
return Text(routeInformation!.location!);
};
await tester.pumpWidget(buildBoilerPlate(
Router<RouteInformation>(
routeInformationProvider: provider,
routeInformationParser: SimpleRouteInformationParser(),
routerDelegate: delegate,
),
));
expect(find.text('initial'), findsOneWidget);
expect(reportedRouteInformation, isNull);
// This will cause the router to rebuild.
provider.value = const RouteInformation(
location: 'update',
);
// This will schedule the route reporting.
delegate.notifyListeners();
await tester.pump();
expect(find.text('initial'), findsNothing);
expect(find.text('update'), findsOneWidget);
// The router should not report because the route name is already up to
// date.
expect(reportedRouteInformation, isNull);
}); });
testWidgets('PlatformRouteInformationProvider works', (WidgetTester tester) async { testWidgets('PlatformRouteInformationProvider works', (WidgetTester tester) async {
...@@ -823,18 +801,33 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -823,18 +801,33 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
); );
log.clear(); log.clear();
provider.routerReportsNewRouteInformation(const RouteInformation(location: 'a', state: true)); provider.routerReportsNewRouteInformation(const RouteInformation(location: 'a', state: true), type: RouteInformationReportingType.none);
// Implicit reporting pushes new history entry if the location changes.
expect(log, <Object>[ expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null), isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': true, 'replace': false }), isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': true, 'replace': false }),
]); ]);
log.clear();
provider.routerReportsNewRouteInformation(const RouteInformation(location: 'a', state: false), type: RouteInformationReportingType.none);
// Since the location is the same, the provider sends replaces message.
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'a', 'state': false, 'replace': true }),
]);
log.clear(); log.clear();
provider.routerReportsNewRouteInformation(const RouteInformation(location: 'b', state: false), isNavigation: false); provider.routerReportsNewRouteInformation(const RouteInformation(location: 'b', state: false), type: RouteInformationReportingType.neglect);
expect(log, <Object>[ expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null), isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'b', 'state': false, 'replace': true }), isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'b', 'state': false, 'replace': true }),
]); ]);
log.clear();
provider.routerReportsNewRouteInformation(const RouteInformation(location: 'b', state: false), type: RouteInformationReportingType.navigate);
expect(log, <Object>[
isMethodCall('selectMultiEntryHistory', arguments: null),
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{ 'location': 'b', 'state': false, 'replace': false }),
]);
}); });
testWidgets('RootBackButtonDispatcher works', (WidgetTester tester) async { testWidgets('RootBackButtonDispatcher works', (WidgetTester tester) async {
...@@ -1239,7 +1232,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -1239,7 +1232,7 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
testWidgets('Router reports location if it is different from location given by OS', (WidgetTester tester) async { testWidgets('Router reports location if it is different from location given by OS', (WidgetTester tester) async {
final List<RouteInformation> reportedRouteInformation = <RouteInformation>[]; final List<RouteInformation> reportedRouteInformation = <RouteInformation>[];
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider( final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider(
onRouterReport: (RouteInformation info, bool isNavigation) => reportedRouteInformation.add(info), onRouterReport: (RouteInformation info, RouteInformationReportingType type) => reportedRouteInformation.add(info),
)..value = const RouteInformation(location: '/home'); )..value = const RouteInformation(location: '/home');
await tester.pumpWidget(buildBoilerPlate( await tester.pumpWidget(buildBoilerPlate(
...@@ -1256,13 +1249,13 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester ...@@ -1256,13 +1249,13 @@ testWidgets('ChildBackButtonDispatcher take priority recursively', (WidgetTester
)); ));
expect(find.text('Current route: /home'), findsOneWidget); expect(find.text('Current route: /home'), findsOneWidget);
expect(reportedRouteInformation, isEmpty); expect(reportedRouteInformation.single.location, '/home');
provider.value = const RouteInformation(location: '/doesNotExist'); provider.value = const RouteInformation(location: '/doesNotExist');
await tester.pump(); await tester.pump();
expect(find.text('Current route: /404'), findsOneWidget); expect(find.text('Current route: /404'), findsOneWidget);
expect(reportedRouteInformation.single.location, '/404'); expect(reportedRouteInformation[1].location, '/404');
}); });
} }
...@@ -1277,7 +1270,7 @@ Widget buildBoilerPlate(Widget child) { ...@@ -1277,7 +1270,7 @@ Widget buildBoilerPlate(Widget child) {
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation?); typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation?);
typedef SimpleRouterDelegatePopRoute = Future<bool> Function(); typedef SimpleRouterDelegatePopRoute = Future<bool> Function();
typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result); typedef SimpleNavigatorRouterDelegatePopPage<T> = bool Function(Route<T> route, T result);
typedef RouterReportRouterInformation = void Function(RouteInformation, bool); typedef RouterReportRouterInformation = void Function(RouteInformation, RouteInformationReportingType);
class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> { class SimpleRouteInformationParser extends RouteInformationParser<RouteInformation> {
SimpleRouteInformationParser(); SimpleRouteInformationParser();
...@@ -1398,9 +1391,9 @@ class SimpleRouteInformationProvider extends RouteInformationProvider with Chang ...@@ -1398,9 +1391,9 @@ class SimpleRouteInformationProvider extends RouteInformationProvider with Chang
} }
@override @override
void routerReportsNewRouteInformation(RouteInformation routeInformation, {bool isNavigation = true}) { void routerReportsNewRouteInformation(RouteInformation routeInformation, {required RouteInformationReportingType type}) {
_value = routeInformation; _value = routeInformation;
onRouterReport?.call(routeInformation, isNavigation); onRouterReport?.call(routeInformation, type);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment