blob: 1afeff1731bdfc86177f53050e4bf5803da7b0d7 [file] [log] [blame]
// 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/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/navigator_utils.dart';
// Matches _kTopGapRatio in cupertino/sheet.dart.
const double _kTopGapRatio = 0.08;
void main() {
testWidgets('Sheet route does not cover the whole screen', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
greaterThan(0.0),
);
});
testWidgets('showDragHandle adds a drag handle to the top of the sheet', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
showDragHandle: true,
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final Finder dragHandleFinder = find.byWidgetPredicate((Widget widget) {
return widget is DecoratedBox &&
widget.decoration is ShapeDecoration &&
(widget.decoration as ShapeDecoration).color == CupertinoColors.tertiaryLabel;
});
expect(dragHandleFinder, findsOneWidget);
});
testWidgets('showDragHandle adds a MediaQuery padding so content can render below the handle', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
showDragHandle: true,
builder: (BuildContext context) {
return const CupertinoPageScaffold(
child: SafeArea(child: Text('Page 2')),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final Finder dragHandleFinder = find.byWidgetPredicate((Widget widget) {
return widget is DecoratedBox &&
widget.decoration is ShapeDecoration &&
(widget.decoration as ShapeDecoration).color == CupertinoColors.tertiaryLabel;
});
final Offset dragHandleOffset = tester.getTopLeft(dragHandleFinder);
final Offset sheetContentOffset = tester.getTopLeft(find.text('Page 2'));
expect(sheetContentOffset.dy, greaterThan(dragHandleOffset.dy));
});
testWidgets('Previous route moves slight downward when sheet route is pushed', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.text('Page 2'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
// Previous page is still visible behind the new sheet.
expect(find.text('Page 1'), findsOneWidget);
final Offset pageOneOffset = tester.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
);
expect(pageOneOffset.dy, greaterThan(0.0));
expect(pageOneOffset.dx, greaterThan(0.0));
expect(find.text('Page 2'), findsOneWidget);
final double pageTwoYOffset = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(pageTwoYOffset, greaterThan(pageOneOffset.dy));
});
testWidgets('If a sheet covers another sheet, then the previous sheet moves slightly upwards', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 3'));
},
),
);
},
child: const Text('Push Page 3'),
),
],
),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.text('Page 2'), findsNothing);
expect(find.text('Page 3'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
expect(find.text('Page 3'), findsNothing);
final double previousPageTwoDY = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
expect(find.text('Page 3'), findsOneWidget);
expect(previousPageTwoDY, greaterThan(0.0));
expect(
previousPageTwoDY,
greaterThan(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
),
);
});
testWidgets('by default showCupertinoSheet does not enable nested navigation', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
context,
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: <Widget>[
const Text('Page 3'),
CupertinoButton(onPressed: () {}, child: const Text('Pop Page 3')),
],
),
);
},
),
);
},
child: const Text('Push Page 3'),
),
],
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(child: sheetScaffoldContent(context));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
expect(find.text('Page 3'), findsNothing);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
greaterThan(0.0),
);
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsNothing);
expect(find.text('Page 3'), findsOneWidget);
// New route should be at the top of the screen.
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 3'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
});
testWidgets('useNestedNavigation set to true enables nested navigation', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
context,
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: <Widget>[
const Text('Page 3'),
CupertinoButton(onPressed: () {}, child: const Text('Pop Page 3')),
],
),
);
},
),
);
},
child: const Text('Push Page 3'),
),
],
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
useNestedNavigation: true,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(child: sheetScaffoldContent(context));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
expect(find.text('Page 3'), findsNothing);
final double pageTwoDY = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(pageTwoDY, greaterThan(0.0));
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsNothing);
expect(find.text('Page 3'), findsOneWidget);
// New route should be at the same height as the previous route.
final double pageThreeDY = tester
.getTopLeft(
find.ancestor(of: find.text('Page 3'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(pageThreeDY, greaterThan(0.0));
expect(pageThreeDY, equals(pageTwoDY));
});
testWidgets('useNestedNavigation handles programmatic pops', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () => Navigator.of(context).maybePop(),
child: const Text('Go Back'),
),
],
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
useNestedNavigation: true,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(child: sheetScaffoldContent(context));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
// The first page is at the top of the screen.
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.text('Page 2'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
// The first page, which is behind the top sheet but still partially visibile, is moved downwards.
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
greaterThan(0.0),
);
await tester.tap(find.text('Go Back'));
await tester.pumpAndSettle();
// The first page would correctly transition back and sit at the top of the screen.
expect(find.text('Page 1'), findsOneWidget);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.text('Page 2'), findsNothing);
});
testWidgets('useNestedNavigation handles system pop gestures', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: <Widget>[
const Text('Page 3'),
CupertinoButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Go back'),
),
],
),
);
},
),
);
},
child: const Text('Push Page 3'),
),
],
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
useNestedNavigation: true,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(child: sheetScaffoldContent(context));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
// The first page is at the top of the screen.
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.text('Page 2'), findsNothing);
expect(find.text('Page 3'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
expect(find.text('Page 3'), findsNothing);
// The first page, which is behind the top sheet but still partially visibile, is moved downwards.
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
greaterThan(0.0),
);
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
expect(find.text('Page 3'), findsOneWidget);
// Simulate a system back gesture.
await simulateSystemBack();
await tester.pumpAndSettle();
// Go back to the first page within the sheet.
expect(find.text('Page 2'), findsOneWidget);
expect(find.text('Page 3'), findsNothing);
// The first page is still stacked behind the sheet.
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
greaterThan(0.0),
);
await simulateSystemBack();
await tester.pumpAndSettle();
// The first page would correctly transition back and sit at the top of the screen.
expect(find.text('Page 1'), findsOneWidget);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.text('Page 2'), findsNothing);
});
testWidgets('sheet has route settings', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/') {
return PageRouteBuilder<void>(
pageBuilder:
(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(middle: Text('Page 1')),
child: Container(),
);
},
);
}
return CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(middle: Text('Page: ${settings.name}')),
child: Container(),
);
},
);
},
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2'), findsNothing);
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pumpAndSettle();
expect(find.text('Page: /next'), findsOneWidget);
});
testWidgets('sheet with RouteSettings', (WidgetTester tester) async {
late RouteSettings currentRouteSetting;
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
navigatorObservers: <NavigatorObserver>[
_ClosureNavigatorObserver(
onDidChange: (Route<dynamic> newRoute) {
currentRouteSetting = newRoute.settings;
},
),
],
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
settings: const RouteSettings(name: 'simpleroute'),
context: scaffoldKey.currentContext!,
pageBuilder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Hello'));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(currentRouteSetting.name, '/');
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Hello'), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Hello'), findsOneWidget);
expect(currentRouteSetting.name, 'simpleroute');
Navigator.of(scaffoldKey.currentContext!).pop();
await tester.pumpAndSettle();
expect(currentRouteSetting.name, '/');
});
testWidgets('content does not go below the bottom of the screen', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(child: Container());
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(tester.getSize(find.byType(Container)).height, 600.0 - (600.0 * _kTopGapRatio));
});
testWidgets('nested navbars remove MediaQuery top padding', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
final GlobalKey appBarKey = GlobalKey();
final GlobalKey sheetBarKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: MediaQuery(
data: const MediaQueryData(padding: EdgeInsets.fromLTRB(0, 20, 0, 0)),
child: CupertinoPageScaffold(
key: scaffoldKey,
navigationBar: CupertinoNavigationBar(
key: appBarKey,
middle: const Text('Navbar'),
backgroundColor: const Color(0xFFF8F8F8),
),
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
key: sheetBarKey,
middle: const Text('Navbar'),
),
child: Container(),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
),
);
final double homeNavBardHeight = tester.getSize(find.byKey(appBarKey)).height;
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
final double sheetNavBarHeight = tester.getSize(find.byKey(sheetBarKey)).height;
expect(sheetNavBarHeight, lessThan(homeNavBardHeight));
});
testWidgets('Previous route corner radius goes to same when sheet route is popped', (
WidgetTester tester,
) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: const Icon(Icons.arrow_back_ios),
),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
)
.dy,
equals(0.0),
);
expect(find.byType(Icon), findsNothing);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
// Previous page is still visible behind the new sheet.
expect(find.text('Page 1'), findsOneWidget);
final Offset pageOneOffset = tester.getTopLeft(
find.ancestor(of: find.text('Page 1'), matching: find.byType(CupertinoPageScaffold)),
);
expect(pageOneOffset.dy, greaterThan(0.0));
expect(pageOneOffset.dx, greaterThan(0.0));
expect(find.byType(Icon), findsOneWidget);
// Pop Sheet Route
await tester.tap(find.byType(Icon));
await tester.pumpAndSettle();
expect(find.byType(ClipRSuperellipse), findsNothing);
expect(find.byType(ClipRRect), findsNothing);
});
testWidgets('Sheet transition does not interfere after popping', (WidgetTester tester) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
final GlobalKey popupMenuButtonKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: CupertinoPageScaffold(
key: homeKey,
child: CupertinoListTile(
onTap: () {
showCupertinoSheet<void>(
context: homeKey.currentContext!,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(
key: sheetKey,
child: const Center(child: Text('Page 2')),
);
},
);
},
title: const Text('ListItem 0'),
trailing: Material(
type: MaterialType.transparency,
child: PopupMenuButton<int>(
key: popupMenuButtonKey,
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<int>>[
const PopupMenuItem<int>(child: Text('Item 0')),
const PopupMenuItem<int>(child: Text('Item 1')),
];
},
),
),
),
),
),
);
await tester.tap(find.text('ListItem 0'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final TestGesture gesture = await tester.startGesture(const Offset(100, 200));
await gesture.moveBy(const Offset(0, 350));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsNothing);
expect(find.text('ListItem 0'), findsOneWidget);
await tester.tap(find.byKey(popupMenuButtonKey));
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(tester.takeException(), isNull);
});
group('drag dismiss gesture', () {
Widget dragGestureApp(GlobalKey homeScaffoldKey, GlobalKey sheetScaffoldKey) {
return CupertinoApp(
home: CupertinoPageScaffold(
key: homeScaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: homeScaffoldKey.currentContext!,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(
key: sheetScaffoldKey,
child: const Center(child: Text('Page 2')),
);
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
}
testWidgets('partial drag and drop does not pop the sheet', (WidgetTester tester) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
await tester.pumpWidget(dragGestureApp(homeKey, sheetKey));
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
var box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double initialPosition = box.localToGlobal(Offset.zero).dy;
final TestGesture gesture = await tester.startGesture(const Offset(100, 200));
// Partial drag down
await gesture.moveBy(const Offset(0, 200));
await tester.pump();
box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double middlePosition = box.localToGlobal(Offset.zero).dy;
expect(middlePosition, greaterThan(initialPosition));
// Release gesture. Sheet should not pop and slide back up.
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double finalPosition = box.localToGlobal(Offset.zero).dy;
expect(finalPosition, lessThan(middlePosition));
expect(finalPosition, equals(initialPosition));
});
testWidgets('dropping the drag further down the page pops the sheet', (
WidgetTester tester,
) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
await tester.pumpWidget(dragGestureApp(homeKey, sheetKey));
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final TestGesture gesture = await tester.startGesture(const Offset(100, 200));
await gesture.moveBy(const Offset(0, 350));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsNothing);
});
testWidgets('dismissing with a drag pops all nested routes', (WidgetTester tester) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.of(context).push(
CupertinoPageRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Center(child: Text('Page 3')));
},
),
);
},
child: const Text('Push Page 3'),
),
],
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: homeKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: homeKey.currentContext!,
useNestedNavigation: true,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(
key: sheetKey,
child: sheetScaffoldContent(context),
);
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
expect(find.text('Page 3'), findsOneWidget);
final TestGesture gesture = await tester.startGesture(const Offset(100, 200));
await gesture.moveBy(const Offset(0, 350));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsNothing);
expect(find.text('Page 3'), findsNothing);
});
testWidgets('Popping the sheet during drag should not crash', (WidgetTester tester) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
await tester.pumpWidget(dragGestureApp(homeKey, sheetKey));
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
final TestGesture gesture = await tester.createGesture();
await gesture.down(const Offset(100, 200));
// Need 2 events to form a valid drag
await tester.pump(const Duration(milliseconds: 100));
await gesture.moveTo(const Offset(100, 300), timeStamp: const Duration(milliseconds: 100));
await tester.pump(const Duration(milliseconds: 200));
await gesture.moveTo(const Offset(100, 500), timeStamp: const Duration(milliseconds: 200));
Navigator.of(homeKey.currentContext!).pop();
await tester.pumpAndSettle();
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2'), findsNothing);
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Page 1'), findsOneWidget);
});
testWidgets('Sheet should not block nested scroll', (WidgetTester tester) async {
final GlobalKey homeKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return ListView(
children: const <Widget>[
Text('Top of Scroll'),
SizedBox(width: double.infinity, height: 100),
Text('Middle of Scroll'),
SizedBox(width: double.infinity, height: 100),
],
);
}
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: homeKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: homeKey.currentContext!,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(child: sheetScaffoldContent(context));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Top of Scroll'), findsOneWidget);
final double startPosition = tester.getTopLeft(find.text('Middle of Scroll')).dy;
final TestGesture gesture = await tester.createGesture();
await gesture.down(const Offset(100, 100));
// Need 2 events to form a valid drag.
await tester.pump(const Duration(milliseconds: 100));
await gesture.moveTo(const Offset(100, 80), timeStamp: const Duration(milliseconds: 100));
await tester.pump(const Duration(milliseconds: 200));
await gesture.moveTo(const Offset(100, 50), timeStamp: const Duration(milliseconds: 200));
await tester.pumpAndSettle();
final double endPosition = tester.getTopLeft(find.text('Middle of Scroll')).dy;
// Final position should be higher.
expect(endPosition, lessThan(startPosition));
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('drag dismiss uses route navigator instead of root navigator', (
WidgetTester tester,
) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey nestedNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey sheetKey = GlobalKey();
var wasPopped = false;
var rootNavigatorPopped = false;
await tester.pumpWidget(
CupertinoApp(
home: PopScope(
onPopInvokedWithResult: (bool didPop, Object? result) {
if (didPop) {
rootNavigatorPopped = true;
}
},
child: CupertinoPageScaffold(
key: homeKey,
child: Navigator(
key: nestedNavigatorKey,
onGenerateRoute: (RouteSettings settings) {
return CupertinoPageRoute<void>(
settings: settings,
builder: (BuildContext context) {
return Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
context,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return PopScope(
onPopInvokedWithResult: (bool didPop, Object? result) {
if (didPop) {
wasPopped = true;
}
},
child: CupertinoPageScaffold(
key: sheetKey,
child: const Center(child: Text('Page 2')),
),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
);
},
);
},
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
expect(wasPopped, false);
expect(rootNavigatorPopped, false);
// Start drag gesture and drag down far enough to trigger dismissal
final TestGesture gesture = await tester.startGesture(const Offset(100, 200));
await gesture.moveBy(const Offset(0, 350));
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
// Verify the sheet was dismissed and the PopScope callback was triggered
expect(find.text('Page 2'), findsNothing);
expect(find.text('Page 1'), findsOneWidget);
// Verify that the nested navigator was used (sheet PopScope triggered)
// but the root navigator was NOT used (root PopScope not triggered)
expect(wasPopped, true);
expect(rootNavigatorPopped, false);
});
testWidgets('dragging does not move the sheet when enableDrag is false', (
WidgetTester tester,
) async {
Widget nonDragGestureApp(GlobalKey homeScaffoldKey, GlobalKey sheetScaffoldKey) {
return CupertinoApp(
home: CupertinoPageScaffold(
key: homeScaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: homeScaffoldKey.currentContext!,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(
key: sheetScaffoldKey,
child: const Center(child: Text('Page 2')),
);
},
enableDrag: false,
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
}
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
await tester.pumpWidget(nonDragGestureApp(homeKey, sheetKey));
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
var box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double initialPosition = box.localToGlobal(Offset.zero).dy;
final TestGesture gesture = await tester.startGesture(const Offset(100, 200));
// Partial drag down
await gesture.moveBy(const Offset(0, 200));
await tester.pump();
// Release gesture. Sheet should not move.
box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double middlePosition = box.localToGlobal(Offset.zero).dy;
expect(middlePosition, equals(initialPosition));
await gesture.up();
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double finalPosition = box.localToGlobal(Offset.zero).dy;
expect(finalPosition, equals(middlePosition));
expect(finalPosition, equals(initialPosition));
});
// Regression test for https://github.com/flutter/flutter/issues/163572.
testWidgets('showCupertinoSheet shows snackbar at bottom of screen', (
WidgetTester tester,
) async {
final scaffoldKey = GlobalKey<ScaffoldMessengerState>();
void showSheet(BuildContext context) {
showCupertinoSheet<void>(
context: context,
pageBuilder: (BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
const Text('Cupertino Sheet'),
CupertinoButton(
onPressed: () {
scaffoldKey.currentState?.showSnackBar(
const SnackBar(content: Text('SnackBar'), backgroundColor: Colors.red),
);
},
child: const Text('Show SnackBar'),
),
],
),
);
},
);
}
await tester.pumpWidget(
MaterialApp(
scaffoldMessengerKey: scaffoldKey,
home: Scaffold(
body: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
Builder(
builder: (BuildContext context) {
return CupertinoButton(
onPressed: () {
showSheet(context);
},
child: const Text('Show Cupertino Sheet'),
);
},
),
],
),
),
),
),
);
expect(find.text('Page 1'), findsOneWidget);
await tester.tap(find.text('Show Cupertino Sheet'));
await tester.pumpAndSettle();
expect(
tester
.getTopLeft(
find.ancestor(of: find.text('Cupertino Sheet'), matching: find.byType(Scaffold)),
)
.dy,
greaterThan(0.0),
);
await tester.tap(find.text('Show SnackBar'));
await tester.pumpAndSettle();
expect(find.byType(SnackBar), findsAtLeast(1));
expect(
tester.getBottomLeft(find.byType(Scaffold).first).dy,
equals(tester.getBottomLeft(find.byType(SnackBar).first).dy),
);
final TestGesture gesture = await tester.startGesture(const Offset(200, 400));
await tester.pump();
expect(
tester.getBottomLeft(find.byType(Scaffold).first).dy,
equals(tester.getBottomLeft(find.byType(SnackBar).first).dy),
);
await gesture.up();
await tester.pumpAndSettle();
expect(
tester.getBottomLeft(find.byType(Scaffold).first).dy,
equals(tester.getBottomLeft(find.byType(SnackBar).first).dy),
);
});
testWidgets('partial upward drag stretches and returns without popping', (
WidgetTester tester,
) async {
final GlobalKey homeKey = GlobalKey();
final GlobalKey sheetKey = GlobalKey();
await tester.pumpWidget(dragGestureApp(homeKey, sheetKey));
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
var box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double initialPosition = box.localToGlobal(Offset.zero).dy;
final TestGesture gesture = await tester.startGesture(const Offset(100, 400));
await gesture.moveBy(const Offset(0, -100));
await tester.pump();
box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double stretchedPosition = box.localToGlobal(Offset.zero).dy;
expect(stretchedPosition, lessThan(initialPosition));
await gesture.up();
await tester.pumpAndSettle();
box = tester.renderObject(find.byKey(sheetKey)) as RenderBox;
final double finalPosition = box.localToGlobal(Offset.zero).dy;
expect(finalPosition, initialPosition);
});
});
testWidgets('CupertinoSheet causes SystemUiOverlayStyle changes', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
navigationBar: const CupertinoNavigationBar(middle: Text('SystemUiOverlayStyle')),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.light);
expect(SystemChrome.latestStyle!.statusBarIconBrightness, Brightness.dark);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.dark);
expect(SystemChrome.latestStyle!.statusBarIconBrightness, Brightness.light);
// Returning to the previous page reverts the system UI.
Navigator.of(scaffoldKey.currentContext!).pop();
await tester.pumpAndSettle();
expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.light);
expect(SystemChrome.latestStyle!.statusBarIconBrightness, Brightness.dark);
});
testWidgets(
'content placed in safe area of showCupertinoSheet is rendered within the safe area bounds',
(WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
Widget sheetScaffoldContent(BuildContext context) {
return const SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(height: 80, width: double.infinity, child: Text('Top container')),
SizedBox(height: 80, width: double.infinity, child: Text('Bottom container')),
],
),
);
}
const double bottomPadding = 50;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
padding: const EdgeInsets.fromLTRB(0, 20, 0, bottomPadding),
viewPadding: const EdgeInsets.fromLTRB(0, 20, 0, bottomPadding),
),
child: CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
pageBuilder: (BuildContext context) {
return CupertinoPageScaffold(child: sheetScaffoldContent(context));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
},
),
);
expect(find.text('Page 1'), findsOneWidget);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
final double pageHeight = tester
.getRect(
find.ancestor(
of: find.text('Top container'),
matching: find.byType(CupertinoPageScaffold),
),
)
.bottom;
expect(
pageHeight -
tester
.getBottomLeft(
find
.ancestor(of: find.text('Bottom container'), matching: find.byType(SizedBox))
.first,
)
.dy,
bottomPadding,
);
},
);
group('topGap parameter tests', () {
testWidgets('sheet uses default topGap when not specified', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final double sheetTopOffset = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
// Should use default topGap ratio (8% of screen height = 0.08 * 600.0 = 48.0)
expect(sheetTopOffset, equals(600.0 * _kTopGapRatio));
});
testWidgets('sheet with custom topGap uses custom positioning', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
topGap: 0.0,
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final double sheetTopOffset = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(sheetTopOffset, equals(0.0));
});
testWidgets('showCupertinoSheet accepts topGap parameter', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
topGap: 0.15,
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 2'));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final double sheetTopOffset = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(sheetTopOffset, equals(600.0 * 0.15));
});
testWidgets('custom topGap disables delegated transitions', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 3'));
},
topGap: 0.1, // Custom topGap should disable transitions
),
);
},
child: const Text('Push Page 3'),
),
],
),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
final double pageTwoYBeforePage3 = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
final double pageTwoYAfterPage3 = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
// Page 2 should remain at the same position because custom topGap disables transitions
expect(pageTwoYAfterPage3, equals(pageTwoYBeforePage3));
final double pageThreeY = tester
.getTopLeft(
find.ancestor(of: find.text('Page 3'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(pageThreeY, equals(600.0 * 0.1));
});
testWidgets('default topGap allows delegated transitions', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: <Widget>[
const Text('Page 2'),
CupertinoButton(
onPressed: () {
Navigator.push<void>(
scaffoldKey.currentContext!,
CupertinoSheetRoute<void>(
builder: (BuildContext context) {
return const CupertinoPageScaffold(child: Text('Page 3'));
},
// No topGap specified - should use default and allow transitions
),
);
},
child: const Text('Push Page 3'),
),
],
),
);
},
),
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
final double pageTwoYBeforePage3 = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
await tester.tap(find.text('Push Page 3'));
await tester.pumpAndSettle();
final double pageTwoYAfterPage3 = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
// Page 2 should move upward because default topGap allows delegated transitions
expect(pageTwoYAfterPage3, lessThan(pageTwoYBeforePage3));
});
testWidgets('topGap affects drag gesture calculations', (WidgetTester tester) async {
final GlobalKey scaffoldKey = GlobalKey();
Widget dragGestureAppWithTopGap(double topGap) {
return CupertinoApp(
home: CupertinoPageScaffold(
key: scaffoldKey,
child: Center(
child: Column(
children: <Widget>[
const Text('Page 1'),
CupertinoButton(
onPressed: () {
showCupertinoSheet<void>(
context: scaffoldKey.currentContext!,
topGap: topGap,
pageBuilder: (BuildContext context) {
return const CupertinoPageScaffold(child: Center(child: Text('Page 2')));
},
);
},
child: const Text('Push Page 2'),
),
],
),
),
),
);
}
// Test with custom topGap of 0.3
await tester.pumpWidget(dragGestureAppWithTopGap(0.3));
await tester.tap(find.text('Push Page 2'));
await tester.pumpAndSettle();
expect(find.text('Page 2'), findsOneWidget);
final double sheetTopOffset = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
expect(sheetTopOffset, equals(600.0 * 0.3));
// Test that drag still works with custom topGap
final TestGesture gesture = await tester.startGesture(const Offset(100, 300));
await gesture.moveBy(const Offset(0, 100));
await tester.pump();
final double draggedPosition = tester
.getTopLeft(
find.ancestor(of: find.text('Page 2'), matching: find.byType(CupertinoPageScaffold)),
)
.dy;
// Sheet should move down when dragged
expect(draggedPosition, greaterThan(sheetTopOffset));
await gesture.up();
await tester.pumpAndSettle();
});
});
testWidgets('didUpdateWidget in sheet transition does not try and use multiple tickers', (
WidgetTester tester,
) async {
final animation = AnimationController(vsync: const TestVSync());
final secondaryAnimation = AnimationController(vsync: const TestVSync());
await tester.pumpWidget(
CupertinoSheetTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
topGap: 0.08,
linearTransition: false,
child: const SizedBox(height: 100, width: 100),
),
);
final newAnimation = AnimationController(vsync: const TestVSync());
// Should not throw an exception.
await tester.pumpWidget(
CupertinoSheetTransition(
primaryRouteAnimation: newAnimation,
secondaryRouteAnimation: secondaryAnimation,
topGap: 0.08,
linearTransition: false,
child: const SizedBox(height: 100, width: 100),
),
);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
animation.dispose();
secondaryAnimation.dispose();
newAnimation.dispose();
});
}
class _ClosureNavigatorObserver extends NavigatorObserver {
_ClosureNavigatorObserver({required this.onDidChange});
final void Function(Route<dynamic> newRoute) onDidChange;
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(route);
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) => onDidChange(previousRoute!);
@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) =>
onDidChange(previousRoute!);
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) => onDidChange(newRoute!);
}