// 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_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; void main() { testWidgets('Verify that a tap on modal barrier dismisses an action sheet', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( const CupertinoActionSheet( title: Text('Action Sheet'), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(find.text('Action Sheet'), findsOneWidget); await tester.tapAt(const Offset(20.0, 20.0)); await tester.pump(); expect(find.text('Action Sheet'), findsNothing); }); testWidgets('Verify that a tap on title section (not buttons) does not dismiss an action sheet', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( const CupertinoActionSheet( title: Text('Action Sheet'), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 5)); expect(find.text('Action Sheet'), findsOneWidget); await tester.tap(find.text('Action Sheet')); await tester.pump(); expect(find.text('Action Sheet'), findsOneWidget); }); testWidgets('Action sheet destructive text style', (WidgetTester tester) async { await tester.pumpWidget( boilerplate( CupertinoActionSheetAction( isDestructiveAction: true, child: const Text('Ok'), onPressed: () { }, ), ), ); final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok')); expect(widget.style.color, const CupertinoDynamicColor.withBrightnessAndContrast( color: Color.fromARGB(255, 255, 59, 48), darkColor: Color.fromARGB(255, 255, 69, 58), highContrastColor: Color.fromARGB(255, 215, 0, 21), darkHighContrastColor: Color.fromARGB(255, 255, 105, 97), )); }); testWidgets('Action sheet dark mode', (WidgetTester tester) async { final Widget action = CupertinoActionSheetAction( child: const Text('action'), onPressed: () {}, ); Brightness brightness = Brightness.light; late StateSetter stateSetter; TextStyle actionTextStyle(String text) { return tester.widget<DefaultTextStyle>( find.descendant( of: find.widgetWithText(CupertinoActionSheetAction, text), matching: find.byType(DefaultTextStyle), ), ).style; } await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( StatefulBuilder( builder: (BuildContext context, StateSetter setter) { stateSetter = setter; return CupertinoTheme( data: CupertinoThemeData( brightness: brightness, primaryColor: const CupertinoDynamicColor.withBrightnessAndContrast( color: Color.fromARGB(255, 0, 122, 255), darkColor: Color.fromARGB(255, 10, 132, 255), highContrastColor: Color.fromARGB(255, 0, 64, 221), darkHighContrastColor: Color.fromARGB(255, 64, 156, 255), ), ), child: CupertinoActionSheet(actions: <Widget>[action]), ); }, ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect( actionTextStyle('action').color!.value, const Color.fromARGB(255, 0, 122, 255).value, ); stateSetter(() { brightness = Brightness.dark; }); await tester.pump(); expect( actionTextStyle('action').color!.value, const Color.fromARGB(255, 10, 132, 255).value, ); }); testWidgets('Action sheet default text style', (WidgetTester tester) async { await tester.pumpWidget( boilerplate( CupertinoActionSheetAction( isDefaultAction: true, child: const Text('Ok'), onPressed: () { }, ), ), ); final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok')); expect(widget.style.fontWeight, equals(FontWeight.w600)); }); testWidgets('Action sheet text styles are correct when both title and message are included', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( const CupertinoActionSheet( title: Text('Action Sheet'), message: Text('An action sheet'), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); final DefaultTextStyle titleStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle, 'Action Sheet')); final DefaultTextStyle messageStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle, 'An action sheet')); expect(titleStyle.style.fontWeight, FontWeight.w600); expect(messageStyle.style.fontWeight, FontWeight.w400); }); testWidgets('Action sheet text styles are correct when title but no message is included', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( const CupertinoActionSheet( title: Text('Action Sheet'), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); final DefaultTextStyle titleStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle, 'Action Sheet')); expect(titleStyle.style.fontWeight, FontWeight.w400); }); testWidgets('Action sheet text styles are correct when message but no title is included', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( const CupertinoActionSheet( message: Text('An action sheet'), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); final DefaultTextStyle messageStyle = tester.firstWidget(find.widgetWithText(DefaultTextStyle, 'An action sheet')); expect(messageStyle.style.fontWeight, FontWeight.w600); }); testWidgets('Content section but no actions', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: const Text('The message.'), messageScrollController: scrollController, ), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Content section should be at the bottom left of action sheet // (minus padding). expect( tester.getBottomLeft(find.byType(ClipRRect)), tester.getBottomLeft(find.byType(CupertinoActionSheet)) - const Offset(-8.0, 10.0), ); // Check that the dialog size is the same as the content section size // (minus padding). expect( tester.getSize(find.byType(ClipRRect)).height, tester.getSize(find.byType(CupertinoActionSheet)).height - 20.0, ); expect( tester.getSize(find.byType(ClipRRect)).width, tester.getSize(find.byType(CupertinoActionSheet)).width - 16.0, ); }); testWidgets('Actions but no content section', (WidgetTester tester) async { final ScrollController actionScrollController = ScrollController(); await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], actionScrollController: actionScrollController, ), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); final Finder finder = find.byElementPredicate( (Element element) { return element.widget.runtimeType.toString() == '_CupertinoAlertActionSection'; }, ); // Check that the title/message section is not displayed (action section is // at the top of the action sheet + padding). expect( tester.getTopLeft(finder), tester.getTopLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, 10.0), ); expect( tester.getTopLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, 10.0), tester.getTopLeft(find.widgetWithText(CupertinoActionSheetAction, 'One')), ); expect( tester.getBottomLeft(find.byType(CupertinoActionSheet)) + const Offset(8.0, -10.0), tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Two')), ); }); testWidgets('Action section is scrollable', (WidgetTester tester) async { final ScrollController actionScrollController = ScrollController(); await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Builder(builder: (BuildContext context) { return MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0), child: CupertinoActionSheet( title: const Text('The title'), message: const Text('The message.'), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Three'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Four'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Five'), onPressed: () { }, ), ], actionScrollController: actionScrollController, ), ); }), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Check that the action buttons list is scrollable. expect(actionScrollController.offset, 0.0); actionScrollController.jumpTo(100.0); expect(actionScrollController.offset, 100.0); actionScrollController.jumpTo(0.0); // Check that the action buttons are aligned vertically. expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'One')).dx, equals(400.0)); expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Two')).dx, equals(400.0)); expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Three')).dx, equals(400.0)); expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Four')).dx, equals(400.0)); expect(tester.getCenter(find.widgetWithText(CupertinoActionSheetAction, 'Five')).dx, equals(400.0)); // Check that the action buttons are the correct heights. expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'One')).height, equals(92.0)); expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Two')).height, equals(92.0)); expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Three')).height, equals(92.0)); expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Four')).height, equals(92.0)); expect(tester.getSize(find.widgetWithText(CupertinoActionSheetAction, 'Five')).height, equals(92.0)); }); testWidgets('Content section is scrollable', (WidgetTester tester) async { final ScrollController messageScrollController = ScrollController(); late double screenHeight; await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Builder(builder: (BuildContext context) { screenHeight = MediaQuery.of(context).size.height; return MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0), child: CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], messageScrollController: messageScrollController, ), ); }), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(messageScrollController.offset, 0.0); messageScrollController.jumpTo(100.0); expect(messageScrollController.offset, 100.0); // Set the scroll position back to zero. messageScrollController.jumpTo(0.0); // Expect the action sheet to take all available height. expect(tester.getSize(find.byType(CupertinoActionSheet)).height, screenHeight); }); testWidgets('CupertinoActionSheet scrollbars controllers should be different', (WidgetTester tester) async { // https://github.com/flutter/flutter/pull/81278 await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), ], ) ), ); await tester.tap(find.text('Go')); await tester.pump(); final List<CupertinoScrollbar> scrollbars = find.descendant( of: find.byType(CupertinoActionSheet), matching: find.byType(CupertinoScrollbar), ).evaluate().map((Element e) => e.widget as CupertinoScrollbar).toList(); expect(scrollbars.length, 2); expect(scrollbars[0].controller != scrollbars[1].controller, isTrue); }); testWidgets('Tap on button calls onPressed', (WidgetTester tester) async { bool wasPressed = false; await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Builder(builder: (BuildContext context) { return CupertinoActionSheet( actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { wasPressed = true; Navigator.pop(context); }, ), ], ); }), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(wasPressed, isFalse); await tester.tap(find.text('One')); expect(wasPressed, isTrue); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(find.text('One'), findsNothing); }); testWidgets('Action sheet width is correct when given infinite horizontal space', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Row( children: <Widget>[ CupertinoActionSheet( actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], ), ], ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(tester.getSize(find.byType(CupertinoActionSheet)).width, 600.0); }); testWidgets('Action sheet height is correct when given infinite vertical space', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Column( children: <Widget>[ CupertinoActionSheet( actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], ), ], ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(tester.getSize(find.byType(CupertinoActionSheet)).height, moreOrLessEquals(132.33333333333334)); }); testWidgets('1 action button with cancel button', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); // Action section is size of one action button. expect(findScrollableActionsSectionRenderBox(tester).size.height, 56.0); }); testWidgets('2 action buttons with cancel button', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(112.33333333333331)); }); testWidgets('3 action buttons with cancel button', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Three'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(168.66666666666669)); }); testWidgets('4+ action buttons with cancel button', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Three'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Four'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(84.33333333333337)); }); testWidgets('1 action button without cancel button', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), ], ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(findScrollableActionsSectionRenderBox(tester).size.height, 56.0); }); testWidgets('2+ action buttons without cancel button', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: Text('Very long content' * 200), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(84.33333333333337)); }); testWidgets('Action sheet with just cancel button is correct', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); // Height should be cancel button height + padding expect(tester.getSize(find.byType(CupertinoActionSheet)).height, 76.0); expect(tester.getSize(find.byType(CupertinoActionSheet)).width, 600.0); }); testWidgets('Cancel button tap calls onPressed', (WidgetTester tester) async { bool wasPressed = false; await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Builder(builder: (BuildContext context) { return CupertinoActionSheet( cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { wasPressed = true; Navigator.pop(context); }, ), ); }), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(wasPressed, isFalse); await tester.tap(find.text('Cancel')); expect(wasPressed, isTrue); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(find.text('Cancel'), findsNothing); }); testWidgets('Layout is correct when cancel button is present', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: const Text('The message'), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Cancel')).dy, 590.0); expect( tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'One')).dy, moreOrLessEquals(469.66666666666663), ); expect(tester.getBottomLeft(find.widgetWithText(CupertinoActionSheetAction, 'Two')).dy, 526.0); }); testWidgets('Enter/exit animation is correct', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: const Text('The message'), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); // Enter animation await tester.tap(find.text('Go')); await tester.pump(); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(325.3, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(320.8, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(319.3, epsilon: 0.1)); // Action sheet has reached final height await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(319.3, epsilon: 0.1)); // Exit animation await tester.tapAt(const Offset(20.0, 20.0)); await tester.pump(); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(319.3, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(449.3, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(544.9, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(582.1, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(593.9, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(598.5, epsilon: 0.1)); // Action sheet has disappeared await tester.pump(const Duration(milliseconds: 60)); expect(find.byType(CupertinoActionSheet), findsNothing); }); testWidgets('Modal barrier is pressed during transition', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: const Text('The message'), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); // Enter animation await tester.tap(find.text('Go')); await tester.pump(); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(337.1, epsilon: 0.1)); // Exit animation await tester.tapAt(const Offset(20.0, 20.0)); await tester.pump(const Duration(milliseconds: 60)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(374.3, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, moreOrLessEquals(470.0, epsilon: 0.1)); await tester.pump(const Duration(milliseconds: 60)); expect(tester.getTopLeft(find.byType(CupertinoActionSheet)).dy, 600.0); // Action sheet has disappeared await tester.pump(const Duration(milliseconds: 60)); expect(find.byType(CupertinoActionSheet), findsNothing); }); testWidgets('Action sheet semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( CupertinoActionSheet( title: const Text('The title'), message: const Text('The message'), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], cancelButton: CupertinoActionSheetAction( child: const Text('Cancel'), onPressed: () { }, ), ), ), ); await tester.tap(find.text('Go')); await tester.pump(); expect( semantics, hasSemantics( TestSemantics.root( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.scopesRoute, SemanticsFlag.namesRoute, ], label: 'Alert', children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.hasImplicitScrolling, ], children: <TestSemantics>[ TestSemantics( label: 'The title', ), TestSemantics( label: 'The message', ), ], ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.hasImplicitScrolling, ], children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.isButton, ], actions: <SemanticsAction>[ SemanticsAction.tap, ], label: 'One', ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.isButton, ], actions: <SemanticsAction>[ SemanticsAction.tap, ], label: 'Two', ), ], ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.isButton, ], actions: <SemanticsAction>[ SemanticsAction.tap, ], label: 'Cancel', ), ], ), ], ), ], ), ], ), ignoreId: true, ignoreRect: true, ignoreTransform: true, ), ); semantics.dispose(); }); testWidgets('Conflicting scrollbars are not applied by ScrollBehavior to CupertinoActionSheet', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/83819 final ScrollController actionScrollController = ScrollController(); await tester.pumpWidget( createAppWithButtonThatLaunchesActionSheet( Builder(builder: (BuildContext context) { return MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0), child: CupertinoActionSheet( title: const Text('The title'), message: const Text('The message.'), actions: <Widget>[ CupertinoActionSheetAction( child: const Text('One'), onPressed: () { }, ), CupertinoActionSheetAction( child: const Text('Two'), onPressed: () { }, ), ], actionScrollController: actionScrollController, ), ); }), ), ); await tester.tap(find.text('Go')); await tester.pump(); // The inherited ScrollBehavior should not apply Scrollbars since they are // already built in to the widget. expect(find.byType(Scrollbar), findsNothing); expect(find.byType(RawScrollbar), findsNothing); // Built in CupertinoScrollbars should only number 2: one for the actions, // one for the content. expect(find.byType(CupertinoScrollbar), findsNWidgets(2)); }, variant: TargetPlatformVariant.all()); } RenderBox findScrollableActionsSectionRenderBox(WidgetTester tester) { final RenderObject actionsSection = tester.renderObject( find.byElementPredicate((Element element) { return element.widget.runtimeType.toString() == '_CupertinoAlertActionSection'; }), ); assert(actionsSection is RenderBox); return actionsSection as RenderBox; } Widget createAppWithButtonThatLaunchesActionSheet(Widget actionSheet) { return CupertinoApp( home: Center( child: Builder(builder: (BuildContext context) { return CupertinoButton( onPressed: () { showCupertinoModalPopup<void>( context: context, builder: (BuildContext context) { return actionSheet; }, ); }, child: const Text('Go'), ); }), ), ); } Widget boilerplate(Widget child) { return Directionality( textDirection: TextDirection.ltr, child: child, ); }