// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; void main() { testWidgets('Material2 - Drawer control test', (WidgetTester tester) async { const containerKey = Key('container'); await tester.pumpWidget( MaterialApp( theme: ThemeData(useMaterial3: false), home: Scaffold( drawer: Drawer( child: ListView( children: [ DrawerHeader( child: Container(key: containerKey, child: const Text('header')), ), const ListTile(leading: Icon(Icons.archive), title: Text('Archive')), ], ), ), ), ), ); expect(find.text('Archive'), findsNothing); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(find.text('Archive'), findsOneWidget); RenderBox box = tester.renderObject(find.byType(DrawerHeader)); expect(box.size.height, equals(160.0 + 8.0 + 1.0)); // height + bottom margin + bottom edge final double drawerWidth = box.size.width; final double drawerHeight = box.size.height; box = tester.renderObject(find.byKey(containerKey)); expect(box.size.width, equals(drawerWidth - 2 * 16.0)); expect(box.size.height, equals(drawerHeight - 2 * 16.0)); expect(find.text('header'), findsOneWidget); }); testWidgets('Material3 - Drawer control test', (WidgetTester tester) async { const containerKey = Key('container'); await tester.pumpWidget( MaterialApp( home: Scaffold( drawer: Drawer( child: ListView( children: [ DrawerHeader( child: Container(key: containerKey, child: const Text('header')), ), const ListTile(leading: Icon(Icons.archive), title: Text('Archive')), ], ), ), ), ), ); expect(find.text('Archive'), findsNothing); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(find.text('Archive'), findsOneWidget); RenderBox box = tester.renderObject(find.byType(DrawerHeader)); expect(box.size.height, equals(160.0 + 8.0 + 1.0)); // height + bottom margin + bottom edge final double drawerWidth = box.size.width; final double drawerHeight = box.size.height; box = tester.renderObject(find.byKey(containerKey)); expect(box.size.width, equals(drawerWidth - 2 * 16.0)); expect( box.size.height, equals(drawerHeight - 2 * 16.0 - 1.0), ); // Header divider thickness is 1.0 in Material 3. expect(find.text('header'), findsOneWidget); }); testWidgets( 'Drawer dismiss barrier has label', (WidgetTester tester) async { final semantics = SemanticsTester(tester); await tester.pumpWidget(const MaterialApp(home: Scaffold(drawer: Drawer()))); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect( semantics, includesNodeWith( label: const DefaultMaterialLocalizations().modalBarrierDismissLabel, actions: [SemanticsAction.tap], ), ); semantics.dispose(); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS, }), ); testWidgets('Drawer dismiss barrier has no label', (WidgetTester tester) async { final semantics = SemanticsTester(tester); await tester.pumpWidget(const MaterialApp(home: Scaffold(drawer: Drawer()))); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect( semantics, isNot( includesNodeWith( label: const DefaultMaterialLocalizations().modalBarrierDismissLabel, actions: [SemanticsAction.tap, SemanticsAction.focus], ), ), ); semantics.dispose(); }, variant: TargetPlatformVariant.only(TargetPlatform.android)); testWidgets('Scaffold drawerScrimColor', (WidgetTester tester) async { // The scrim is a ColoredBox within a Semantics node labeled "Dismiss", // within a DrawerController. Sorry. Widget getScrim() { return tester .widget( find.descendant( of: find.byType(DrawerController), matching: find.byWidgetPredicate((Widget widget) { return widget is Semantics && widget.properties.label == 'Dismiss'; }), ), ) .child!; } final scaffoldKey = GlobalKey(); Widget buildFrame({Color? drawerScrimColor}) { return MaterialApp( home: Scaffold( key: scaffoldKey, drawerScrimColor: drawerScrimColor, drawer: Drawer( child: Builder( builder: (BuildContext context) { return GestureDetector( onTap: () { Navigator.pop(context); }, // close drawer ); }, ), ), ), ); } Future checkScrim(Color color) async { scaffoldKey.currentState!.openDrawer(); await tester.pump(); var scrim = getScrim() as ColoredBox; expect(scrim.color, isSameColorAs(color.withValues(alpha: 0))); await tester.pumpAndSettle(); scrim = getScrim() as ColoredBox; expect(scrim.color, isSameColorAs(color)); await tester.tap(find.byType(Drawer)); await tester.pumpAndSettle(); expect(find.byType(Drawer), findsNothing); } // Default drawerScrimColor await tester.pumpWidget(buildFrame()); await checkScrim(Colors.black54); // Specific drawerScrimColor await tester.pumpWidget(buildFrame(drawerScrimColor: const Color(0xFF323232))); await checkScrim(const Color(0xFF323232)); }); testWidgets('Open/close drawers by flinging', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( drawer: Drawer(child: Text('start drawer')), endDrawer: Drawer(child: Text('end drawer')), ), ), ); // In the beginning, drawers are closed final ScaffoldState state = tester.firstState(find.byType(Scaffold)); expect(state.isDrawerOpen, equals(false)); expect(state.isEndDrawerOpen, equals(false)); final Size size = tester.getSize(find.byType(Scaffold)); // A fling from the left opens the start drawer await tester.flingFrom(Offset(0, size.height / 2), const Offset(80, 0), 500); await tester.pumpAndSettle(); expect(state.isDrawerOpen, equals(true)); expect(state.isEndDrawerOpen, equals(false)); // Now, a fling from the right closes the drawer await tester.flingFrom(Offset(size.width - 1, size.height / 2), const Offset(-80, 0), 500); await tester.pumpAndSettle(); expect(state.isDrawerOpen, equals(false)); expect(state.isEndDrawerOpen, equals(false)); // Another fling from the right opens the end drawer await tester.flingFrom(Offset(size.width - 1, size.height / 2), const Offset(-80, 0), 500); await tester.pumpAndSettle(); expect(state.isDrawerOpen, equals(false)); expect(state.isEndDrawerOpen, equals(true)); // And a fling from the left closes it await tester.flingFrom(Offset(0, size.height / 2), const Offset(80, 0), 500); await tester.pumpAndSettle(); expect(state.isDrawerOpen, equals(false)); expect(state.isEndDrawerOpen, equals(false)); }); testWidgets('Open/close drawer by dragging', (WidgetTester tester) async { final draggable = ThemeData(platform: TargetPlatform.android); await tester.pumpWidget( MaterialApp( theme: draggable, home: const Scaffold(drawer: Drawer()), ), ); final TestGesture gesture = await tester.createGesture(); final Finder finder = find.byType(Drawer); double drawerPosition() { expect(finder, findsOneWidget); final RenderBox renderBox = tester.renderObject(finder); return renderBox.localToGlobal(Offset.zero).dx; } // Pointer down (drawer is closed). await gesture.addPointer(); await gesture.down(const Offset(2, 2)); await tester.pump(); expect(finder, findsNothing); // Open drawer slightly. await gesture.moveBy(const Offset(20, 0)); await tester.pump(); expect(drawerPosition(), isNegative); // Open drawer more than halfway. await gesture.moveBy(const Offset(200, 0)); await tester.pump(); expect(drawerPosition(), isNegative); // Drawer is fully open. await gesture.moveBy(const Offset(200, 0)); await tester.pump(); expect(drawerPosition(), 0.0); // Drawer is less than halfway closed. await gesture.moveBy(const Offset(-100.0, 0)); await tester.pump(); expect(drawerPosition(), moreOrLessEquals(-100.0)); // Drawer is more than halfway closed. await gesture.moveBy(const Offset(-100.0, 0)); await tester.pump(); expect(drawerPosition(), moreOrLessEquals(-200.0)); // Drawer is completely closed. await gesture.moveTo(Offset.zero); await tester.pump(); expect(finder, findsNothing); }); testWidgets('Scaffold.drawer - null restorationId ', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( restorationScopeId: 'app', home: Scaffold(key: scaffoldKey, drawer: const Text('drawer'), body: Container()), ), ); await tester.pump(); // no effect expect(find.text('drawer'), findsNothing); scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); await tester.restartAndRestore(); // Drawer state should not have been saved. expect(find.text('drawer'), findsNothing); }); testWidgets('Scaffold.endDrawer - null restorationId ', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( restorationScopeId: 'app', home: Scaffold(key: scaffoldKey, drawer: const Text('endDrawer'), body: Container()), ), ); await tester.pump(); // no effect expect(find.text('endDrawer'), findsNothing); scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsOneWidget); await tester.restartAndRestore(); // Drawer state should not have been saved. expect(find.text('endDrawer'), findsNothing); }); testWidgets('Scaffold.drawer state restoration test', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( restorationScopeId: 'app', home: Scaffold( key: scaffoldKey, restorationId: 'scaffold', drawer: const Text('drawer'), body: Container(), ), ), ); await tester.pump(); // no effect expect(find.text('drawer'), findsNothing); scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); await tester.restartAndRestore(); expect(find.text('drawer'), findsOneWidget); final TestRestorationData data = await tester.getRestorationData(); await tester.tapAt(const Offset(750.0, 100.0)); // on the mask await tester.pumpAndSettle(); expect(find.text('drawer'), findsNothing); await tester.restoreFrom(data); expect(find.text('drawer'), findsOneWidget); }); testWidgets('Scaffold.endDrawer state restoration test', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( restorationScopeId: 'app', home: Scaffold( key: scaffoldKey, restorationId: 'scaffold', endDrawer: const Text('endDrawer'), body: Container(), ), ), ); await tester.pump(); // no effect expect(find.text('endDrawer'), findsNothing); scaffoldKey.currentState!.openEndDrawer(); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsOneWidget); await tester.restartAndRestore(); expect(find.text('endDrawer'), findsOneWidget); final TestRestorationData data = await tester.getRestorationData(); await tester.tapAt(const Offset(750.0, 100.0)); // on the mask await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsNothing); await tester.restoreFrom(data); expect(find.text('endDrawer'), findsOneWidget); }); testWidgets('Both drawer and endDrawer state restoration test', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( restorationScopeId: 'app', home: Scaffold( restorationId: 'scaffold', key: scaffoldKey, drawer: const Text('drawer'), endDrawer: const Text('endDrawer'), body: Container(), ), ), ); await tester.pump(); // no effect expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsNothing); scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); expect(find.text('endDrawer'), findsNothing); await tester.restartAndRestore(); expect(find.text('drawer'), findsOneWidget); expect(find.text('endDrawer'), findsNothing); TestRestorationData data = await tester.getRestorationData(); await tester.tapAt(const Offset(750.0, 100.0)); // on the mask await tester.pumpAndSettle(); expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsNothing); await tester.restoreFrom(data); expect(find.text('drawer'), findsOneWidget); expect(find.text('endDrawer'), findsNothing); await tester.tapAt(const Offset(750.0, 100.0)); // on the mask await tester.pumpAndSettle(); expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsNothing); scaffoldKey.currentState!.openEndDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsOneWidget); await tester.restartAndRestore(); expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsOneWidget); data = await tester.getRestorationData(); await tester.tapAt(const Offset(750.0, 100.0)); // on the mask await tester.pumpAndSettle(); expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsNothing); await tester.restoreFrom(data); expect(find.text('drawer'), findsNothing); expect(find.text('endDrawer'), findsOneWidget); }); testWidgets('ScaffoldState close drawer', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold(key: scaffoldKey, drawer: const Text('Drawer'), body: Container()), ), ); expect(find.text('Drawer'), findsNothing); scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('Drawer'), findsOneWidget); scaffoldKey.currentState!.closeDrawer(); await tester.pumpAndSettle(); expect(find.text('Drawer'), findsNothing); }); testWidgets('ScaffoldState close drawer do not crash if drawer is already closed', ( WidgetTester tester, ) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold(key: scaffoldKey, drawer: const Text('Drawer'), body: Container()), ), ); expect(find.text('Drawer'), findsNothing); scaffoldKey.currentState!.closeDrawer(); await tester.pumpAndSettle(); expect(find.text('Drawer'), findsNothing); }); testWidgets('Disposing drawer does not crash if drawer is open and framework is locked', ( WidgetTester tester, ) async { // Regression test for https://github.com/flutter/flutter/issues/34978 addTearDown(tester.view.reset); tester.view.physicalSize = const Size(1800.0, 2400.0); await tester.pumpWidget( MaterialApp( home: OrientationBuilder( builder: (BuildContext context, Orientation orientation) { switch (orientation) { case Orientation.portrait: return Scaffold(drawer: const Text('drawer'), body: Container()); case Orientation.landscape: return Scaffold(appBar: AppBar(), body: Container()); } }, ), ), ); expect(find.text('drawer'), findsNothing); // Using a global key is a workaround for this issue. final ScaffoldState portraitScaffoldState = tester.firstState(find.byType(Scaffold)); portraitScaffoldState.openDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); // Change the orientation and cause the drawer controller to be disposed // while the framework is locked. tester.view.physicalSize = const Size(2400.0, 1800.0); await tester.pumpAndSettle(); expect(find.byType(BackButton), findsNothing); }); testWidgets('Disposing endDrawer does not crash if endDrawer is open and framework is locked', ( WidgetTester tester, ) async { // Regression test for https://github.com/flutter/flutter/issues/34978 addTearDown(tester.view.reset); tester.view.physicalSize = const Size(1800.0, 2400.0); await tester.pumpWidget( MaterialApp( home: OrientationBuilder( builder: (BuildContext context, Orientation orientation) { switch (orientation) { case Orientation.portrait: return Scaffold(endDrawer: const Text('endDrawer'), body: Container()); case Orientation.landscape: return Scaffold(appBar: AppBar(), body: Container()); } }, ), ), ); expect(find.text('endDrawer'), findsNothing); // Using a global key is a workaround for this issue. final ScaffoldState portraitScaffoldState = tester.firstState(find.byType(Scaffold)); portraitScaffoldState.openEndDrawer(); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsOneWidget); // Change the orientation and cause the drawer controller to be disposed // while the framework is locked. tester.view.physicalSize = const Size(2400.0, 1800.0); await tester.pumpAndSettle(); expect(find.byType(BackButton), findsNothing); }); testWidgets('ScaffoldState close end drawer', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold(key: scaffoldKey, endDrawer: const Text('endDrawer'), body: Container()), ), ); expect(find.text('endDrawer'), findsNothing); scaffoldKey.currentState!.openEndDrawer(); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsOneWidget); scaffoldKey.currentState!.closeEndDrawer(); await tester.pumpAndSettle(); expect(find.text('endDrawer'), findsNothing); }); testWidgets('Drawer width defaults to Material spec', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: Scaffold(drawer: Drawer()))); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); final RenderBox box = tester.renderObject(find.byType(Drawer)); expect(box.size.width, equals(304.0)); }); testWidgets('Drawer width can be customized by parameter', (WidgetTester tester) async { const double smallWidth = 200; await tester.pumpWidget( const MaterialApp( home: Scaffold(drawer: Drawer(width: smallWidth)), ), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); state.openDrawer(); await tester.pumpAndSettle(); final RenderBox box = tester.renderObject(find.byType(Drawer)); expect(box.size.width, equals(smallWidth)); }); testWidgets('Material3 - Drawer default shape (ltr)', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Directionality( textDirection: TextDirection.ltr, child: Scaffold(drawer: Drawer(), endDrawer: Drawer()), ), ), ); final Finder drawerMaterial = find.descendant( of: find.byType(Drawer), matching: find.byType(Material), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); // Open the drawer. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test the drawer shape. Material material = tester.widget(drawerMaterial); expect( material.shape, const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: Radius.circular(16.0), bottomRight: Radius.circular(16.0), ), ), ); // Close the opened drawer. await tester.tapAt(const Offset(750, 300)); await tester.pumpAndSettle(); // Open the end drawer. state.openEndDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test the end drawer shape. material = tester.widget(drawerMaterial); expect( material.shape, const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16.0), bottomLeft: Radius.circular(16.0), ), ), ); }); testWidgets('Material3 - Drawer default shape (rtl)', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Directionality( textDirection: TextDirection.rtl, child: Scaffold(drawer: Drawer(), endDrawer: Drawer()), ), ), ); final Finder drawerMaterial = find.descendant( of: find.byType(Drawer), matching: find.byType(Material), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); // Open the drawer. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test the drawer shape. Material material = tester.widget(drawerMaterial); expect( material.shape, const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16.0), bottomLeft: Radius.circular(16.0), ), ), ); // Close the opened drawer. await tester.tapAt(const Offset(750, 300)); await tester.pumpAndSettle(); // Open the end drawer. state.openEndDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test the end drawer shape. material = tester.widget(drawerMaterial); expect( material.shape, const RoundedRectangleBorder( borderRadius: BorderRadius.only( topRight: Radius.circular(16.0), bottomRight: Radius.circular(16.0), ), ), ); }); testWidgets('Material3 - Drawer clip behavior', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: Scaffold(drawer: Drawer()))); final Finder drawerMaterial = find.descendant( of: find.byType(Drawer), matching: find.byType(Material), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); // Open the drawer. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test default clip behavior. Material material = tester.widget(drawerMaterial); expect(material.clipBehavior, Clip.hardEdge); state.closeDrawer(); await tester.pumpAndSettle(); // Provide a custom clip behavior. await tester.pumpWidget( const MaterialApp( home: Scaffold(drawer: Drawer(clipBehavior: Clip.antiAlias)), ), ); // Open the drawer again. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Clip behavior is now updated. material = tester.widget(drawerMaterial); expect(material.clipBehavior, Clip.antiAlias); }); testWidgets('Drawer barrier is dismissible by default', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar( title: Semantics(headingLevel: 1, child: const Text('Drawer Dismissible')), ), endDrawer: const Drawer(backgroundColor: Colors.white, width: 300, child: Text('Drawer')), body: Container( color: Colors.white, width: 600, height: 600, child: const Center(child: Text('Drawer Dismissible')), ), ), ), ); // Check the flag is set at the Scaffold level. final Scaffold scaffold = tester.widget(find.byType(Scaffold)); expect(scaffold.drawerBarrierDismissible, true); // Verify whether the drawer barrier is dimissible by default via the state final ScaffoldState state = tester.firstState(find.byType(Scaffold)); expect(state.isDrawerBarrierDismissible, true); // Open the drawer initially. state.openEndDrawer(); await tester.pumpAndSettle(); // Check that the drawer open. expect(find.byType(Drawer), findsExactly(1)); // Close the drawer programmatically. state.closeEndDrawer(); await tester.pumpAndSettle(); expect(find.byType(Drawer), findsExactly(0)); // Open it again, and make sure the drawer is available. state.openEndDrawer(); await tester.pumpAndSettle(); expect(find.byType(Drawer), findsExactly(1)); // Find the ModalBarrier. final Finder modalBarrierFinder = find.byType(ModalBarrier); // Get the RenderBox of the ModalBarrier. final modalBarrierRenderBox = tester.renderObject(modalBarrierFinder) as RenderBox; // Calculate a point to tap outside the Drawer. // This example taps on the ModalBarrier somewhere outside its boundaries. const modalBarrierCenter = Offset(400, 300); final Offset tapPosition = modalBarrierRenderBox.localToGlobal(modalBarrierCenter); // Tap on the ModalBarrier. await tester.tapAt(tapPosition); await tester.pumpAndSettle(); // Make sure the drawer is gone, since the drawerBarrierDismissible flag is set to true by default. expect(find.byType(Drawer), findsExactly(0)); }); testWidgets('Drawer can be configured as not dismissible', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(useMaterial3: false), home: Scaffold( drawerBarrierDismissible: false, appBar: AppBar( title: Semantics(headingLevel: 1, child: const Text('Drawer Dismissible')), ), endDrawer: const Drawer(backgroundColor: Colors.white, width: 300, child: Text('Drawer')), body: Container( color: Colors.white, width: 600, height: 600, child: const Center(child: Text('Drawer Dismissible')), ), ), ), ); // Make sure the flag is set to false at the Scaffold level. final Scaffold scaffold = tester.widget(find.byType(Scaffold)); expect(scaffold.drawerBarrierDismissible, false); // Verify the drawer barrier is not dimissible by checking the state's getter final ScaffoldState state = tester.firstState(find.byType(Scaffold)); expect(state.isDrawerBarrierDismissible, false); // Open the drawer initially. state.openEndDrawer(); await tester.pumpAndSettle(); // Check that the drawer is open. expect(find.byType(Drawer), findsExactly(1)); // Close the drawer programmatically. state.closeEndDrawer(); await tester.pumpAndSettle(); expect(find.byType(Drawer), findsExactly(0)); // Open it again, and make sure the drawer is available. state.openEndDrawer(); await tester.pumpAndSettle(); expect(find.byType(Drawer), findsExactly(1)); // Find the ModalBarrier. final Finder modalBarrierFinder = find.byType(ModalBarrier); // Get the RenderBox of the ModalBarrier. final modalBarrierRenderBox = tester.renderObject(modalBarrierFinder) as RenderBox; // Calculate a point to tap outside the Drawer. // This example taps on the ModalBarrier somewhere outside its boundaries. const modalBarrierCenter = Offset(400, 300); final Offset tapPosition = modalBarrierRenderBox.localToGlobal(modalBarrierCenter); // Tap on the ModalBarrier. await tester.tapAt(tapPosition); await tester.pumpAndSettle(); // Make sure the drawer is still present, and that tapping on the modal barrier // didn't dismiss it, since the drawerBarrierDismissible property is set to false. expect(find.byType(Drawer), findsExactly(1)); }); testWidgets('Drawer can be dismissed with the escape key by default', ( WidgetTester tester, ) async { final scaffoldKey = GlobalKey(); // Test with drawerBarrierDismissible: true (default) await tester.pumpWidget( MaterialApp( home: Scaffold( key: scaffoldKey, drawer: const Drawer(child: Text('drawer')), ), ), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); expect(state.isDrawerBarrierDismissible, isTrue); // Open the drawer. scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); // Close the drawer with the escape key. await tester.sendKeyEvent(LogicalKeyboardKey.escape); await tester.pumpAndSettle(); expect(find.text('drawer'), findsNothing); }); testWidgets( 'Drawer cannot be dismissed with the escape key when drawerBarrierDismissible is false', (WidgetTester tester) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( key: scaffoldKey, drawer: const Drawer(child: Text('drawer')), drawerBarrierDismissible: false, ), ), ); // Verify that the [Scaffold.drawerBarrierDismissible] flag is false final ScaffoldState state = tester.firstState(find.byType(Scaffold)); expect(state.isDrawerBarrierDismissible, isFalse); // Open the drawer. scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); // Try to close the drawer with the escape key, and verify it does not close. await tester.sendKeyEvent(LogicalKeyboardKey.escape); await tester.pumpAndSettle(); expect(find.text('drawer'), findsOneWidget); }, ); // Regression test for https://github.com/flutter/flutter/issues/177005 testWidgets('Drawer semantics for mismatched platforms', (WidgetTester tester) async { const localizations = DefaultMaterialLocalizations(); Future pumpDrawerWithTheme(TargetPlatform themePlatform) async { final scaffoldKey = GlobalKey(); await tester.pumpWidget( MaterialApp( theme: ThemeData(platform: themePlatform), home: Scaffold( key: scaffoldKey, drawer: const Drawer(child: Text('Drawer')), body: Container(), ), ), ); scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); // Test label semantics. final Finder drawerLabelFinder = find.bySemanticsLabel(localizations.drawerLabel); if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { expect(drawerLabelFinder, findsNothing); // Apple platforms don't show drawer label. } else { expect(drawerLabelFinder, findsOneWidget); // Non-Apple platforms show drawer label. } // Test barrier semantics. final semantics = SemanticsTester(tester); final expectBarrierExcluded = defaultTargetPlatform == TargetPlatform.android; if (expectBarrierExcluded) { expect( semantics, isNot( includesNodeWith( label: localizations.modalBarrierDismissLabel, actions: [SemanticsAction.tap, SemanticsAction.focus], ), ), ); } else { expect( semantics, includesNodeWith( label: localizations.modalBarrierDismissLabel, actions: [SemanticsAction.tap], ), ); } semantics.dispose(); } // Test with theme.platform = Android on different real platforms. await pumpDrawerWithTheme(TargetPlatform.android); // Test with theme.platform = iOS on different real platforms. await pumpDrawerWithTheme(TargetPlatform.iOS); }, variant: TargetPlatformVariant.all()); group('Material 2', () { // These tests are only relevant for Material 2. Once Material 2 // support is deprecated and the APIs are removed, these tests // can be deleted. testWidgets('Material2 - Drawer default shape', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold(drawer: Drawer(), endDrawer: Drawer()), ), ); final Finder drawerMaterial = find.descendant( of: find.byType(Drawer), matching: find.byType(Material), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); // Open the drawer. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test the drawer shape. Material material = tester.widget(drawerMaterial); expect(material.shape, null); // Close the opened drawer. await tester.tapAt(const Offset(750, 300)); await tester.pumpAndSettle(); // Open the end drawer. state.openEndDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test the end drawer shape. material = tester.widget(drawerMaterial); expect(material.shape, null); }); testWidgets('Material2 - Drawer clip behavior', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold(drawer: Drawer()), ), ); final Finder drawerMaterial = find.descendant( of: find.byType(Drawer), matching: find.byType(Material), ); final ScaffoldState state = tester.firstState(find.byType(Scaffold)); // Open the drawer. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Test default clip behavior. Material material = tester.widget(drawerMaterial); expect(material.clipBehavior, Clip.none); state.closeDrawer(); await tester.pumpAndSettle(); // Provide a shape and custom clip behavior. await tester.pumpWidget( MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( drawer: Drawer(clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder()), ), ), ); // Open the drawer again. state.openDrawer(); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Clip behavior is now updated. material = tester.widget(drawerMaterial); expect(material.clipBehavior, Clip.hardEdge); }); }); // Regression test for https://github.com/flutter/flutter/issues/6537 testWidgets('Drawer and DrawerHeader do not crash at zero area', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Center( child: SizedBox.shrink( child: Drawer(child: DrawerHeader(child: Text('X'))), ), ), ), ); expect(tester.getSize(find.byType(Drawer)), Size.zero); expect(tester.getSize(find.byType(DrawerHeader)), Size.zero); }); }