| // 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!); |
| } |