1342 lines
50 KiB
Dart
1342 lines
50 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
testWidgets('Restoration Smoke Test', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 2), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 3), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
expect(findRoute('home', count: 2), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('restorablePushNamed', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo', arguments: 3);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 0, arguments: 3), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2, arguments: 3), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar', arguments: 4);
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar', arguments: 4), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
});
|
|
|
|
testWidgets('restorablePushReplacementNamed', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushReplacementNamed('Foo', arguments: 3);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0, arguments: 3), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2, arguments: 3), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar', arguments: 4);
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar', arguments: 4), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
});
|
|
|
|
testWidgets('restorablePopAndPushNamed', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePopAndPushNamed('Foo', arguments: 3);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0, arguments: 3), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2, arguments: 3), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar', arguments: 4);
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar', arguments: 4), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
});
|
|
|
|
testWidgets('restorablePushNamedAndRemoveUntil', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamedAndRemoveUntil('Foo', (Route<dynamic> _) => false, arguments: 3);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0, arguments: 3), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2, arguments: 3), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar', arguments: 4);
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar', arguments: 4), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1, arguments: 3), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
});
|
|
|
|
testWidgets('restorablePush', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePush(_routeBuilder, arguments: 'Foo');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 0), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar'), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('restorablePush adds route on all platforms', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePush(_routeBuilder, arguments: 'Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('restorablePushReplacement', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushReplacement(_routeBuilder, arguments: 'Foo');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar'), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('restorablePushReplacement adds route on all platforms', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushReplacement(_routeBuilder, arguments: 'Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('restorablePushAndRemoveUntil', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushAndRemoveUntil(_routeBuilder, (Route<dynamic> _) => false, arguments: 'Foo');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar'), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('restorablePushAndRemoveUntil adds route on all platforms', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushAndRemoveUntil(_routeBuilder, (Route<dynamic> _) => false, arguments: 'Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('restorableReplace', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
final Route<Object> oldRoute = ModalRoute.of(tester.element(find.text('Route: home')))!;
|
|
expect(oldRoute.settings.name, 'home');
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorableReplace(newRouteBuilder: _routeBuilder, arguments: 'Foo', oldRoute: oldRoute);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0), findsOneWidget);
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar'), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 1), findsOneWidget);
|
|
expect(findRoute('Bar'), findsNothing);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('restorableReplace adds route on all platforms', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
final Route<Object> oldRoute = ModalRoute.of(tester.element(find.text('Route: home')))!;
|
|
expect(oldRoute.settings.name, 'home');
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorableReplace(newRouteBuilder: _routeBuilder, arguments: 'Foo', oldRoute: oldRoute);
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('restorableReplaceRouteBelow', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Anchor');
|
|
await tester.pumpAndSettle();
|
|
|
|
await tapRouteCounter('Anchor', tester);
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 0, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Anchor', count: 1), findsOneWidget);
|
|
|
|
final Route<Object> anchor = ModalRoute.of(tester.element(find.text('Route: Anchor')))!;
|
|
expect(anchor.settings.name, 'Anchor');
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorableReplaceRouteBelow(newRouteBuilder: _routeBuilder, arguments: 'Foo', anchorRoute: anchor);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Anchor', count: 1), findsOneWidget);
|
|
|
|
await tapRouteCounter('Anchor', tester);
|
|
expect(findRoute('Anchor', count: 2), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Anchor', count: 2), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Anchor', tester);
|
|
expect(findRoute('Anchor', count: 3), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar'), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 0, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Anchor', count: 2), findsOneWidget);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('restorableReplaceRouteBelow adds route on all platforms', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Anchor');
|
|
await tester.pumpAndSettle();
|
|
|
|
await tapRouteCounter('Anchor', tester);
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 0, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Anchor', count: 1), findsOneWidget);
|
|
|
|
final Route<Object> anchor = ModalRoute.of(tester.element(find.text('Route: Anchor')))!;
|
|
expect(anchor.settings.name, 'Anchor');
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorableReplaceRouteBelow(newRouteBuilder: _routeBuilder, arguments: 'Foo', anchorRoute: anchor);
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo', skipOffstage: false), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('restoring a popped route', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('home'), findsNothing);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 3), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsNothing);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('popped routes are not restored', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Bar'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('Bar'), findsNothing);
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', skipOffstage: false), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('Bar'), findsNothing);
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', skipOffstage: false), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('routes that are in the process of push are restored', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
|
|
// Push is in progress.
|
|
final ModalRoute<Object> route1 = ModalRoute.of(tester.element(find.text('Route: Foo')))!;
|
|
final String route1id = route1.restorationScopeId.value!;
|
|
expect(route1id, isNotNull);
|
|
expect(route1.settings.name, 'Foo');
|
|
expect(route1.animation!.isCompleted, isFalse);
|
|
expect(route1.animation!.isDismissed, isFalse);
|
|
expect(route1.isActive, isTrue);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', skipOffstage: false), findsOneWidget);
|
|
final ModalRoute<Object> route2 = ModalRoute.of(tester.element(find.text('Route: Foo')))!;
|
|
expect(route2, isNot(same(route1)));
|
|
expect(route1.restorationScopeId.value, route1id);
|
|
expect(route2.animation!.isCompleted, isTrue);
|
|
expect(route2.isActive, isTrue);
|
|
});
|
|
|
|
testWidgets('routes that are in the process of pop are not restored', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
|
|
final ModalRoute<Object> route1 = ModalRoute.of(tester.element(find.text('Route: Foo')))!;
|
|
int notifyCount = 0;
|
|
route1.restorationScopeId.addListener(() {
|
|
notifyCount++;
|
|
});
|
|
expect(route1.isActive, isTrue);
|
|
expect(route1.restorationScopeId.value, isNotNull);
|
|
expect(route1.animation!.isCompleted, isTrue);
|
|
expect(notifyCount, 0);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
expect(notifyCount, 1);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
// Pop is in progress.
|
|
expect(route1.restorationScopeId.value, isNull);
|
|
expect(route1.settings.name, 'Foo');
|
|
expect(route1.animation!.isCompleted, isFalse);
|
|
expect(route1.animation!.isDismissed, isFalse);
|
|
expect(route1.isActive, isFalse);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('Foo', skipOffstage: false), findsNothing);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
expect(notifyCount, 1);
|
|
});
|
|
|
|
testWidgets('routes are restored in the right order', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route1');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route1'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route2');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route2'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route3');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route3'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route4');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route4'), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('route4'), findsOneWidget);
|
|
expect(findRoute('route3', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('route2', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('route1', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('home', skipOffstage: false), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route3'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route2'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route1'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('home'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('all routes up to first unrestorable are restored', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route1');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route1'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route2');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route2'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('route3');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route3'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route4');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route4'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePush(_routeBuilder, arguments: 'route5');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route5'), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('route5', skipOffstage: false), findsNothing);
|
|
expect(findRoute('route4', skipOffstage: false), findsNothing);
|
|
expect(findRoute('route3', skipOffstage: false), findsNothing);
|
|
|
|
expect(findRoute('route2'), findsOneWidget);
|
|
expect(findRoute('route1', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('home', skipOffstage: false), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('removing unrestorable routes restores all of them', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route1');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route1'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route2');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route2'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('route3');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route3'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route4');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route4'), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('route5');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('route5'), findsOneWidget);
|
|
|
|
final Route<Object> route = ModalRoute.of(tester.element(find.text('Route: route3', skipOffstage: false)))!;
|
|
expect(route.settings.name, 'route3');
|
|
tester.state<NavigatorState>(find.byType(Navigator)).removeRoute(route);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('route5'), findsOneWidget);
|
|
expect(findRoute('route4', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('route3', skipOffstage: false), findsNothing);
|
|
expect(findRoute('route2', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('route1', skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('home', skipOffstage: false), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('RestorableRouteFuture', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePush(_routeFutureBuilder);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Return value: null'), findsOneWidget);
|
|
|
|
final RestorableRouteFuture<int> routeFuture = tester
|
|
.state<RouteFutureWidgetState>(find.byType(RouteFutureWidget))
|
|
.routeFuture;
|
|
expect(routeFuture.route, isNull);
|
|
expect(routeFuture.isPresent, isFalse);
|
|
expect(routeFuture.enabled, isFalse);
|
|
|
|
routeFuture.present('Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Route: Foo'), findsOneWidget);
|
|
expect(routeFuture.route!.settings.name, 'Foo');
|
|
expect(routeFuture.isPresent, isTrue);
|
|
expect(routeFuture.enabled, isTrue);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(find.text('Route: Foo'), findsOneWidget);
|
|
final RestorableRouteFuture<int> restoredRouteFuture = tester
|
|
.state<RouteFutureWidgetState>(find.byType(RouteFutureWidget, skipOffstage: false))
|
|
.routeFuture;
|
|
expect(restoredRouteFuture.route!.settings.name, 'Foo');
|
|
expect(restoredRouteFuture.isPresent, isTrue);
|
|
expect(restoredRouteFuture.enabled, isTrue);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop(10);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Return value: 10'), findsOneWidget);
|
|
expect(restoredRouteFuture.route, isNull);
|
|
expect(restoredRouteFuture.isPresent, isFalse);
|
|
expect(restoredRouteFuture.enabled, isFalse);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('RestorableRouteFuture in unrestorable context', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
expect(findRoute('home'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('unrestorable');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('unrestorable'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePush(_routeFutureBuilder);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Return value: null'), findsOneWidget);
|
|
|
|
final RestorableRouteFuture<int> routeFuture = tester
|
|
.state<RouteFutureWidgetState>(find.byType(RouteFutureWidget))
|
|
.routeFuture;
|
|
expect(routeFuture.route, isNull);
|
|
expect(routeFuture.isPresent, isFalse);
|
|
expect(routeFuture.enabled, isFalse);
|
|
|
|
routeFuture.present('Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Route: Foo'), findsOneWidget);
|
|
expect(routeFuture.route!.settings.name, 'Foo');
|
|
expect(routeFuture.isPresent, isTrue);
|
|
expect(routeFuture.enabled, isFalse);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Illegal arguments throw', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const TestWidget());
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Bar');
|
|
await tester.pumpAndSettle();
|
|
|
|
final Route<Object> oldRoute = ModalRoute.of(tester.element(find.text('Route: Bar')))!;
|
|
expect(oldRoute.settings.name, 'Bar');
|
|
|
|
final Matcher throwsArgumentsAssertionError = throwsA(isAssertionError.having(
|
|
(AssertionError e) => e.message,
|
|
'message',
|
|
'The arguments object must be serializable via the StandardMessageCodec.',
|
|
));
|
|
final Matcher throwsBuilderAssertionError = throwsA(isAssertionError.having(
|
|
(AssertionError e) => e.message,
|
|
'message',
|
|
'The provided routeBuilder must be a static function.',
|
|
));
|
|
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo', arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushReplacementNamed('Foo', arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePopAndPushNamed('Foo', arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamedAndRemoveUntil('Foo', (Route<Object?> _) => false, arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePush(_routeBuilder, arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushReplacement(_routeBuilder, arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushAndRemoveUntil(_routeBuilder, (Route<Object?> _) => false, arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorableReplace(newRouteBuilder: _routeBuilder, oldRoute: oldRoute, arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorableReplaceRouteBelow(newRouteBuilder: _routeBuilder, anchorRoute: oldRoute, arguments: Object()),
|
|
throwsArgumentsAssertionError,
|
|
);
|
|
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePush((BuildContext _, Object? __) => FakeRoute()),
|
|
throwsBuilderAssertionError,
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/33615
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushReplacement((BuildContext _, Object? __) => FakeRoute()),
|
|
throwsBuilderAssertionError,
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/33615
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorablePushAndRemoveUntil((BuildContext _, Object? __) => FakeRoute(), (Route<Object?> _) => false),
|
|
throwsBuilderAssertionError,
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/33615
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorableReplace(newRouteBuilder: (BuildContext _, Object? __) => FakeRoute(), oldRoute: oldRoute),
|
|
throwsBuilderAssertionError,
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/33615
|
|
);
|
|
expect(
|
|
() => tester.state<NavigatorState>(find.byType(Navigator)).restorableReplaceRouteBelow(newRouteBuilder: (BuildContext _, Object? __) => FakeRoute(), anchorRoute: oldRoute),
|
|
throwsBuilderAssertionError,
|
|
skip: isBrowser, // https://github.com/flutter/flutter/issues/33615
|
|
);
|
|
});
|
|
|
|
testWidgets('Moving scopes', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const RootRestorationScope(
|
|
restorationId: 'root',
|
|
child: TestWidget(
|
|
restorationId: null,
|
|
),
|
|
));
|
|
await tapRouteCounter('home', tester);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
|
|
// Nothing is restored.
|
|
await tester.restartAndRestore();
|
|
expect(findRoute('Foo'), findsNothing);
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
|
|
await tapRouteCounter('home', tester);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
|
|
// Move navigator into restoration scope.
|
|
await tester.pumpWidget(const RootRestorationScope(
|
|
restorationId: 'root',
|
|
child: TestWidget(),
|
|
));
|
|
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
|
|
// Everything is restored.
|
|
await tester.restartAndRestore();
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
|
|
// Move navigator out of restoration scope.
|
|
await tester.pumpWidget(const RootRestorationScope(
|
|
restorationId: 'root',
|
|
child: TestWidget(
|
|
restorationId: null,
|
|
),
|
|
));
|
|
|
|
expect(findRoute('Foo'), findsOneWidget);
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
|
|
// Nothing is restored.
|
|
await tester.restartAndRestore();
|
|
expect(findRoute('Foo'), findsNothing);
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Restoring pages', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const PagedTestWidget());
|
|
expect(findRoute('home', count: 0), findsOneWidget);
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('Foo', tester);
|
|
await tapRouteCounter('Foo', tester);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('bar');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('bar', tester);
|
|
expect(findRoute('bar', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('Foo');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('Foo', count: 0), findsOneWidget);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(findRoute('bar', skipOffstage: false), findsNothing);
|
|
expect(findRoute('Foo', count: 2), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('bar');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('bar', count: 0), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Unrestorable pages', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const PagedTestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('p1');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('p1', tester);
|
|
expect(findRoute('p1', count: 1), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('r1');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('r1', tester);
|
|
expect(findRoute('r1', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('p2', restoreState: false);
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('p2', tester);
|
|
expect(findRoute('p2', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('r2');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('r2', tester);
|
|
expect(findRoute('r2', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('p3');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('p3', tester);
|
|
expect(findRoute('p3', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('r3');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('r3', tester);
|
|
expect(findRoute('r3', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('r2', skipOffstage: false), findsNothing);
|
|
expect(findRoute('r3', count: 1), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('p3', count: 1), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('p2', count: 0), findsOneWidget); // Page did not restore its state!
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('r1', count: 1), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('p1', count: 1), findsOneWidget);
|
|
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('removed page is not restored', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const PagedTestWidget());
|
|
await tapRouteCounter('home', tester);
|
|
expect(findRoute('home', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('p1');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('p1', tester);
|
|
expect(findRoute('p1', count: 1), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).restorablePushNamed('r1');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('r1', tester);
|
|
expect(findRoute('r1', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('p2');
|
|
await tester.pumpAndSettle();
|
|
await tapRouteCounter('p2', tester);
|
|
expect(findRoute('p2', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).removePage('p1');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('p1', count: 1, skipOffstage: false), findsNothing);
|
|
expect(findRoute('r1', count: 1, skipOffstage: false), findsNothing);
|
|
expect(findRoute('p2', count: 1), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(findRoute('home', count: 1, skipOffstage: false), findsOneWidget);
|
|
expect(findRoute('p1', count: 1, skipOffstage: false), findsNothing);
|
|
expect(findRoute('r1', count: 1, skipOffstage: false), findsNothing);
|
|
expect(findRoute('p2', count: 1), findsOneWidget);
|
|
|
|
tester.state<PagedTestNavigatorState>(find.byType(PagedTestNavigator)).addPage('p1');
|
|
await tester.pumpAndSettle();
|
|
expect(findRoute('p1', count: 0), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Helpful assert thrown all routes in onGenerateInitialRoutes are not restorable', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
restorationScopeId: 'material_app',
|
|
initialRoute: '/',
|
|
routes: <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => Container(),
|
|
},
|
|
onGenerateInitialRoutes: (String initialRoute) {
|
|
return <MaterialPageRoute<void>>[
|
|
MaterialPageRoute<void>(
|
|
builder: (BuildContext context) => Container(),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
);
|
|
await tester.restartAndRestore();
|
|
final dynamic exception = tester.takeException();
|
|
expect(exception, isAssertionError);
|
|
expect(
|
|
(exception as AssertionError).message,
|
|
contains('All routes returned by onGenerateInitialRoutes are not restorable.'),
|
|
);
|
|
|
|
// The previous assert leaves the widget tree in a broken state, so the
|
|
// following code catches any remaining exceptions from attempting to build
|
|
// new widget tree.
|
|
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
|
|
dynamic remainingException;
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
remainingException ??= details.exception;
|
|
};
|
|
await tester.pumpWidget(Container(key: UniqueKey()));
|
|
FlutterError.onError = oldHandler;
|
|
expect(remainingException, isAssertionError);
|
|
});
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
Route<void> _routeBuilder(BuildContext context, Object? arguments) {
|
|
return MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return RouteWidget(
|
|
name: arguments! as String,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
Route<void> _routeFutureBuilder(BuildContext context, Object? arguments) {
|
|
return MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return const RouteFutureWidget();
|
|
},
|
|
);
|
|
}
|
|
|
|
class PagedTestWidget extends StatelessWidget {
|
|
const PagedTestWidget({super.key, this.restorationId = 'app'});
|
|
|
|
final String restorationId;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return RootRestorationScope(
|
|
restorationId: restorationId,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: MediaQueryData.fromView(View.of(context)),
|
|
child: const PagedTestNavigator(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class PagedTestNavigator extends StatefulWidget {
|
|
const PagedTestNavigator({super.key});
|
|
|
|
@override
|
|
State<PagedTestNavigator> createState() => PagedTestNavigatorState();
|
|
}
|
|
|
|
class PagedTestNavigatorState extends State<PagedTestNavigator> with RestorationMixin {
|
|
final RestorableString _routes = RestorableString('r-home');
|
|
|
|
void addPage(String name, {bool restoreState = true, int? index}) {
|
|
assert(!name.contains(','));
|
|
assert(!name.startsWith('r-'));
|
|
final List<String> routes = _routes.value.split(',');
|
|
name = restoreState ? 'r-$name' : name;
|
|
if (index != null) {
|
|
routes.insert(index, name);
|
|
} else {
|
|
routes.add(name);
|
|
}
|
|
setState(() {
|
|
_routes.value = routes.join(',');
|
|
});
|
|
}
|
|
|
|
bool removePage(String name) {
|
|
final List<String> routes = _routes.value.split(',');
|
|
if (routes.remove(name) || routes.remove('r-$name')) {
|
|
setState(() {
|
|
_routes.value = routes.join(',');
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Navigator(
|
|
restorationScopeId: 'nav',
|
|
onPopPage: (Route<dynamic> route, dynamic result) {
|
|
if (route.didPop(result)) {
|
|
removePage(route.settings.name!);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
pages: _routes.value.isEmpty ? const <Page<Object?>>[] : _routes.value.split(',').map((String name) {
|
|
if (name.startsWith('r-')) {
|
|
name = name.substring(2);
|
|
return TestPage(
|
|
name: name,
|
|
restorationId: name,
|
|
key: ValueKey<String>(name),
|
|
);
|
|
}
|
|
return TestPage(
|
|
name: name,
|
|
key: ValueKey<String>(name),
|
|
);
|
|
}).toList(),
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<int>(
|
|
settings: settings,
|
|
builder: (BuildContext context) {
|
|
return RouteWidget(
|
|
name: settings.name!,
|
|
arguments: settings.arguments,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
String get restorationId => 'router';
|
|
|
|
@override
|
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
registerForRestoration(_routes, 'routes');
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
_routes.dispose();
|
|
}
|
|
}
|
|
|
|
class TestPage extends Page<void> {
|
|
const TestPage({super.key, required String super.name, super.restorationId});
|
|
|
|
@override
|
|
Route<void> createRoute(BuildContext context) {
|
|
return MaterialPageRoute<void>(
|
|
settings: this,
|
|
builder: (BuildContext context) {
|
|
return RouteWidget(
|
|
name: name!,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class TestWidget extends StatelessWidget {
|
|
const TestWidget({super.key, this.restorationId = 'app'});
|
|
|
|
final String? restorationId;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return RootRestorationScope(
|
|
restorationId: restorationId,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: MediaQueryData.fromView(View.of(context)),
|
|
child: Navigator(
|
|
initialRoute: 'home',
|
|
restorationScopeId: 'app',
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<int>(
|
|
settings: settings,
|
|
builder: (BuildContext context) {
|
|
return RouteWidget(
|
|
name: settings.name!,
|
|
arguments: settings.arguments,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RouteWidget extends StatefulWidget {
|
|
const RouteWidget({super.key, required this.name, this.arguments});
|
|
|
|
final String name;
|
|
final Object? arguments;
|
|
|
|
@override
|
|
State<RouteWidget> createState() => RouteWidgetState();
|
|
}
|
|
|
|
class RouteWidgetState extends State<RouteWidget> with RestorationMixin {
|
|
final RestorableInt counter = RestorableInt(0);
|
|
|
|
@override
|
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
registerForRestoration(counter, 'counter');
|
|
}
|
|
|
|
@override
|
|
String get restorationId => 'stateful';
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
counter.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
GestureDetector(
|
|
child: Text('Route: ${widget.name}'),
|
|
onTap: () {
|
|
setState(() {
|
|
counter.value++;
|
|
});
|
|
},
|
|
),
|
|
if (widget.arguments != null)
|
|
Text('Arguments(home): ${widget.arguments}'),
|
|
Text('Counter(${widget.name}): ${counter.value}'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RouteFutureWidget extends StatefulWidget {
|
|
const RouteFutureWidget({super.key});
|
|
|
|
@override
|
|
State<RouteFutureWidget> createState() => RouteFutureWidgetState();
|
|
}
|
|
|
|
class RouteFutureWidgetState extends State<RouteFutureWidget> with RestorationMixin {
|
|
late RestorableRouteFuture<int> routeFuture;
|
|
int? value;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
routeFuture = RestorableRouteFuture<int>(
|
|
onPresent: (NavigatorState navigatorState, Object? arguments) {
|
|
return navigatorState.restorablePushNamed(arguments! as String);
|
|
},
|
|
onComplete: (int i) {
|
|
setState(() {
|
|
value = i;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
registerForRestoration(routeFuture, 'routeFuture');
|
|
}
|
|
|
|
@override
|
|
String get restorationId => 'routefuturewidget';
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
routeFuture.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Center(
|
|
child: Text('Return value: $value'),
|
|
);
|
|
}
|
|
}
|
|
|
|
Finder findRoute(String name, { Object? arguments, int? count, bool skipOffstage = true }) => _RouteFinder(name, arguments: arguments, count: count, skipOffstage: skipOffstage);
|
|
|
|
Future<void> tapRouteCounter(String name, WidgetTester tester) async {
|
|
await tester.tap(find.text('Route: $name'));
|
|
await tester.pump();
|
|
}
|
|
|
|
class _RouteFinder extends MatchFinder {
|
|
_RouteFinder(this.name, { this.arguments, this.count, super.skipOffstage });
|
|
|
|
final String name;
|
|
final Object? arguments;
|
|
final int? count;
|
|
|
|
@override
|
|
String get description {
|
|
String result = 'Route(name: $name';
|
|
if (arguments != null) {
|
|
result += ', arguments: $arguments';
|
|
}
|
|
if (count != null) {
|
|
result += ', count: $count';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
bool matches(Element candidate) {
|
|
final Widget widget = candidate.widget;
|
|
if (widget is RouteWidget) {
|
|
if (widget.name != name) {
|
|
return false;
|
|
}
|
|
if (arguments != null && widget.arguments != arguments) {
|
|
return false;
|
|
}
|
|
final RouteWidgetState state = (candidate as StatefulElement).state as RouteWidgetState;
|
|
if (count != null && state.counter.value != count) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class FakeRoute extends Fake implements Route<void> { }
|