Reland "Implements the navigator page api (#50362)" (#53708)

This reverts commit aee9e94c21.
This commit is contained in:
chunhtai 2020-04-01 09:44:22 -07:00 committed by GitHub
parent a1f6c173ba
commit a206bf7e2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 1735 additions and 45 deletions

File diff suppressed because it is too large Load diff

View file

@ -1708,6 +1708,666 @@ void main() {
await tester.pump(const Duration(seconds: 1));
expect(tickCount, 4);
});
group('Page api', (){
Widget buildNavigator(
List<Page<dynamic>> pages,
PopPageCallback onPopPage, [
GlobalKey<NavigatorState> key,
TransitionDelegate<dynamic> transitionDelegate
]) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate
],
child: Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
key: key,
pages: pages,
onPopPage: onPopPage,
transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
),
),
),
);
}
testWidgets('can initialize with pages list', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
final List<TestPage> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name:'initial'),
const TestPage(key: ValueKey<String>('2'), name:'second'),
const TestPage(key: ValueKey<String>('3'), name:'third'),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
expect(find.text('third'), findsOneWidget);
expect(find.text('second'), findsNothing);
expect(find.text('initial'), findsNothing);
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('third'), findsNothing);
expect(find.text('second'), findsOneWidget);
expect(find.text('initial'), findsNothing);
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('third'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('initial'), findsOneWidget);
});
testWidgets('can push and pop pages using page api', (WidgetTester tester) async {
Animation<double> secondaryAnimationOfRouteOne;
Animation<double> primaryAnimationOfRouteOne;
Animation<double> secondaryAnimationOfRouteTwo;
Animation<double> primaryAnimationOfRouteTwo;
Animation<double> secondaryAnimationOfRouteThree;
Animation<double> primaryAnimationOfRouteThree;
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<Page<dynamic>> myPages = <Page<dynamic>>[
CustomBuilderPage<void>(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
},
),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
expect(find.text('initial'), findsOneWidget);
myPages = <Page<dynamic>>[
CustomBuilderPage<void>(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
},
),
CustomBuilderPage<void>(
key: const ValueKey<String>('2'),
name:'second',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
);
},
),
CustomBuilderPage<void>(
key: const ValueKey<String>('3'),
name:'third',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteThree = secondaryAnimation;
primaryAnimationOfRouteThree = animation;
return const Text('third');
},
);
},
)
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// The third page is transitioning, and the secondary animation of first
// page should chain with the third page. The animation of second page
// won't start until the third page finishes transition.
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteThree.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.dismissed);
expect(secondaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.forward);
await tester.pump(const Duration(milliseconds: 30));
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteThree.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.dismissed);
expect(secondaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteThree.value, 0.1);
await tester.pumpAndSettle();
// After transition finishes, the routes' animations are correctly chained.
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteThree.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
expect(find.text('third'), findsOneWidget);
expect(find.text('second'), findsNothing);
expect(find.text('initial'), findsNothing);
// Starts pops the pages using page api and verify the animations chain
// correctly.
myPages = <Page<dynamic>>[
CustomBuilderPage<void>(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
},
),
CustomBuilderPage<void>(
key: const ValueKey<String>('2'),
name:'second',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
);
},
),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
await tester.pump(const Duration(milliseconds: 30));
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteThree.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteThree.value, 0.9);
await tester.pumpAndSettle();
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteThree.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
});
testWidgets('can modify routes history and secondary animation still works', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
Animation<double> secondaryAnimationOfRouteOne;
Animation<double> primaryAnimationOfRouteOne;
Animation<double> secondaryAnimationOfRouteTwo;
Animation<double> primaryAnimationOfRouteTwo;
Animation<double> secondaryAnimationOfRouteThree;
Animation<double> primaryAnimationOfRouteThree;
List<Page<dynamic>> myPages = <CustomBuilderPage<void>>[
CustomBuilderPage<void>(
key: const ValueKey<String>('1'),
name:'initial',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteOne = secondaryAnimation;
primaryAnimationOfRouteOne = animation;
return const Text('initial');
},
);
},
),
CustomBuilderPage<void>(
key: const ValueKey<String>('2'),
name:'second',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteTwo = secondaryAnimation;
primaryAnimationOfRouteTwo = animation;
return const Text('second');
},
);
},
),
CustomBuilderPage<void>(
key: const ValueKey<String>('3'),
name:'third',
routeBuilder: (BuildContext context, RouteSettings settings) {
return PageRouteBuilder<void>(
settings: settings,
pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) {
secondaryAnimationOfRouteThree = secondaryAnimation;
primaryAnimationOfRouteThree = animation;
return const Text('third');
},
);
},
),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
expect(find.text('third'), findsOneWidget);
expect(find.text('second'), findsNothing);
expect(find.text('initial'), findsNothing);
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteThree.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteThree.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
myPages = myPages.reversed.toList();
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// Reversed routes are still chained up correctly.
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteOne.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
navigator.currentState.pop();
await tester.pump();
await tester.pump(const Duration(milliseconds: 30));
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteOne.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteOne.value, 0.9);
await tester.pumpAndSettle();
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteOne.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
navigator.currentState.pop();
await tester.pump();
await tester.pump(const Duration(milliseconds: 30));
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteOne.value);
expect(primaryAnimationOfRouteTwo.value, 0.9);
expect(secondaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
await tester.pumpAndSettle();
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
expect(secondaryAnimationOfRouteTwo.value, primaryAnimationOfRouteOne.value);
expect(primaryAnimationOfRouteTwo.status, AnimationStatus.dismissed);
expect(secondaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.dismissed);
});
testWidgets('can work with pageless route', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<TestPage> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name:'initial'),
const TestPage(key: ValueKey<String>('2'), name:'second'),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
expect(find.text('second'), findsOneWidget);
expect(find.text('initial'), findsNothing);
// Pushes two pageless routes to second page route
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('second-pageless1'),
settings: null,
)
);
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('second-pageless2'),
settings: null,
)
);
await tester.pumpAndSettle();
// Now the history should look like
// [initial, second, second-pageless1, second-pageless2].
expect(find.text('initial'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsOneWidget);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name:'initial'),
const TestPage(key: ValueKey<String>('2'), name:'second'),
const TestPage(key: ValueKey<String>('3'), name:'third'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
await tester.pumpAndSettle();
expect(find.text('initial'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsNothing);
expect(find.text('third'), findsOneWidget);
// Pushes one pageless routes to third page route
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('third-pageless1'),
settings: null,
)
);
await tester.pumpAndSettle();
// Now the history should look like
// [initial, second, second-pageless1, second-pageless2, third, third-pageless1].
expect(find.text('initial'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsNothing);
expect(find.text('third'), findsNothing);
expect(find.text('third-pageless1'), findsOneWidget);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name:'initial'),
const TestPage(key: ValueKey<String>('3'), name:'third'),
const TestPage(key: ValueKey<String>('2'), name:'second'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// Swaps the order without any adding or removing should not trigger any
// transition. The routes should update without a pumpAndSettle
// Now the history should look like
// [initial, third, third-pageless1, second, second-pageless1, second-pageless2].
expect(find.text('initial'), findsNothing);
expect(find.text('third'), findsNothing);
expect(find.text('third-pageless1'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsOneWidget);
// Pops the route one by one to make sure the order is correct.
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('initial'), findsNothing);
expect(find.text('third'), findsNothing);
expect(find.text('third-pageless1'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsOneWidget);
expect(find.text('second-pageless2'), findsNothing);
expect(myPages.length, 3);
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('initial'), findsNothing);
expect(find.text('third'), findsNothing);
expect(find.text('third-pageless1'), findsNothing);
expect(find.text('second'), findsOneWidget);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsNothing);
expect(myPages.length, 3);
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('initial'), findsNothing);
expect(find.text('third'), findsNothing);
expect(find.text('third-pageless1'), findsOneWidget);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsNothing);
expect(myPages.length, 2);
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('initial'), findsNothing);
expect(find.text('third'), findsOneWidget);
expect(find.text('third-pageless1'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsNothing);
expect(myPages.length, 2);
navigator.currentState.pop();
await tester.pumpAndSettle();
expect(find.text('initial'), findsOneWidget);
expect(find.text('third'), findsNothing);
expect(find.text('third-pageless1'), findsNothing);
expect(find.text('second'), findsNothing);
expect(find.text('second-pageless1'), findsNothing);
expect(find.text('second-pageless2'), findsNothing);
expect(myPages.length, 1);
});
testWidgets('complex case 1', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<TestPage> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
// Add initial page route with one pageless route.
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
bool initialPageless1Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('initial-pageless1'),
settings: null,
)
).then((_) => initialPageless1Completed = true);
await tester.pumpAndSettle();
// Pushes second page route with two pageless routes.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
await tester.pumpAndSettle();
bool secondPageless1Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('second-pageless1'),
settings: null,
)
).then((_) => secondPageless1Completed = true);
await tester.pumpAndSettle();
bool secondPageless2Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('second-pageless2'),
settings: null,
)
).then((_) => secondPageless2Completed = true);
await tester.pumpAndSettle();
// Pushes third page route with one pageless route.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
const TestPage(key: ValueKey<String>('3'), name: 'third'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
await tester.pumpAndSettle();
bool thirdPageless1Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('third-pageless1'),
settings: null,
)
).then((_) => thirdPageless1Completed = true);
await tester.pumpAndSettle();
// Nothing has been popped.
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
expect(secondPageless2Completed, false);
expect(thirdPageless1Completed, false);
// Switches order and removes the initial page route.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('3'), name: 'third'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
// The pageless route of initial page route should be completed.
expect(initialPageless1Completed, true);
expect(secondPageless1Completed, false);
expect(secondPageless2Completed, false);
expect(thirdPageless1Completed, false);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('3'), name: 'third'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
await tester.pumpAndSettle();
expect(secondPageless1Completed, true);
expect(secondPageless2Completed, true);
expect(thirdPageless1Completed, false);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('4'), name: 'forth'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator));
expect(thirdPageless1Completed, true);
await tester.pumpAndSettle();
expect(find.text('forth'), findsOneWidget);
});
testWidgets('complex case 1 - with always remove transition delegate', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
final AlwaysRemoveTransitionDelegate transitionDelegate = AlwaysRemoveTransitionDelegate();
List<TestPage> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
];
bool onPopPage(Route<dynamic> route, dynamic result) {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result);
}
// Add initial page route with one pageless route.
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate));
bool initialPageless1Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('initial-pageless1'),
settings: null,
)
).then((_) => initialPageless1Completed = true);
await tester.pumpAndSettle();
// Pushes second page route with two pageless routes.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate));
bool secondPageless1Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('second-pageless1'),
settings: null,
)
).then((_) => secondPageless1Completed = true);
await tester.pumpAndSettle();
bool secondPageless2Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('second-pageless2'),
settings: null,
)
).then((_) => secondPageless2Completed = true);
await tester.pumpAndSettle();
// Pushes third page route with one pageless route.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
const TestPage(key: ValueKey<String>('3'), name: 'third'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate));
bool thirdPageless1Completed = false;
navigator.currentState.push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const Text('third-pageless1'),
settings: null,
)
).then((_) => thirdPageless1Completed = true);
await tester.pumpAndSettle();
// Nothing has been popped.
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
expect(secondPageless2Completed, false);
expect(thirdPageless1Completed, false);
// Switches order and removes the initial page route.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('3'), name: 'third'),
const TestPage(key: ValueKey<String>('2'), name: 'second'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate));
// The pageless route of initial page route should be removed without complete.
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
expect(secondPageless2Completed, false);
expect(thirdPageless1Completed, false);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('3'), name: 'third'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate));
await tester.pumpAndSettle();
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
expect(secondPageless2Completed, false);
expect(thirdPageless1Completed, false);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('4'), name: 'forth'),
];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate));
await tester.pump();
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
expect(secondPageless2Completed, false);
expect(thirdPageless1Completed, false);
expect(find.text('forth'), findsOneWidget);
});
});
}
class _TickingWidget extends StatefulWidget {
@ -1742,6 +2402,62 @@ class _TickingWidgetState extends State<_TickingWidget> with SingleTickerProvide
}
}
class AlwaysRemoveTransitionDelegate extends TransitionDelegate<void> {
@override
Iterable<RouteTransitionRecord> resolve({
List<RouteTransitionRecord> newPageRouteHistory,
Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
}) {
final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
void handleExitingRoute(RouteTransitionRecord location) {
if (!locationToExitingPageRoute.containsKey(location))
return;
final RouteTransitionRecord exitingPageRoute = locationToExitingPageRoute[location];
final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute);
exitingPageRoute.markForRemove();
results.add(exitingPageRoute);
if (hasPagelessRoute) {
final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
pagelessRoute.markForRemove();
}
}
handleExitingRoute(exitingPageRoute);
}
handleExitingRoute(null);
for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
if (pageRoute.isEntering) {
pageRoute.markForAdd();
}
results.add(pageRoute);
handleExitingRoute(pageRoute);
}
return results;
}
}
class TestPage extends Page<void> {
const TestPage({
LocalKey key,
String name,
Object arguments,
}) : super(key: key, name: name, arguments: arguments);
@override
Route<void> createRoute(BuildContext context) {
return MaterialPageRoute<void>(
builder: (BuildContext context) => Text(name),
settings: this,
);
}
}
class NoAnimationPageRoute extends PageRouteBuilder<void> {
NoAnimationPageRoute({WidgetBuilder pageBuilder})
: super(pageBuilder: (BuildContext context, __, ___) {