1556 lines
57 KiB
Dart
1556 lines
57 KiB
Dart
// 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.
|
|
|
|
// reduced-test-set:
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'package:clock/clock.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
|
|
const kOpenScale = 1.15;
|
|
const kMinScaleFactor = 1.02;
|
|
|
|
Widget getChild({double width = 300.0, double height = 100.0}) {
|
|
return Container(width: width, height: height, color: CupertinoColors.activeOrange);
|
|
}
|
|
|
|
List<Widget> getActions({int number = 10}) {
|
|
return List<Widget>.generate(
|
|
number,
|
|
(int index) => CupertinoContextMenuAction(child: Text('Action $index')),
|
|
);
|
|
}
|
|
|
|
Widget getBuilder(BuildContext context, Animation<double> animation) {
|
|
return getChild();
|
|
}
|
|
|
|
Widget getContextMenu({
|
|
Alignment alignment = Alignment.center,
|
|
Size screenSize = const Size(800.0, 600.0),
|
|
Widget? child,
|
|
}) {
|
|
return CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: MediaQuery(
|
|
data: MediaQueryData(size: screenSize),
|
|
child: Align(
|
|
alignment: alignment,
|
|
child: CupertinoContextMenu(
|
|
actions: <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction $alignment')),
|
|
],
|
|
child: child ?? getChild(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget getBuilderContextMenu({
|
|
Alignment alignment = Alignment.center,
|
|
Size screenSize = const Size(800.0, 600.0),
|
|
CupertinoContextMenuBuilder? builder,
|
|
}) {
|
|
return CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: MediaQuery(
|
|
data: MediaQueryData(size: screenSize),
|
|
child: Align(
|
|
alignment: alignment,
|
|
child: CupertinoContextMenu.builder(
|
|
actions: <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction $alignment')),
|
|
],
|
|
builder: builder ?? getBuilder,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Finds the child widget that is rendered inside of _DecoyChild.
|
|
Finder findDecoyChild(Widget child) {
|
|
return find.descendant(
|
|
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
matching: find.byWidget(child),
|
|
);
|
|
}
|
|
|
|
// Finds the child widget rendered inside of _ContextMenuRouteStatic.
|
|
Finder findStatic() {
|
|
return find.descendant(
|
|
of: find.byType(CupertinoApp),
|
|
matching: find.byWidgetPredicate(
|
|
(Widget w) => '${w.runtimeType}' == '_ContextMenuRouteStatic',
|
|
),
|
|
);
|
|
}
|
|
|
|
Finder findStaticChild(Widget child) {
|
|
return find.descendant(of: findStatic(), matching: find.byWidget(child));
|
|
}
|
|
|
|
Finder findStaticChildColor(WidgetTester tester) {
|
|
return find.descendant(
|
|
of: findStatic(),
|
|
matching: find.byWidgetPredicate(
|
|
(Widget widget) => widget is ColoredBox && widget.color != CupertinoColors.activeOrange,
|
|
),
|
|
);
|
|
}
|
|
|
|
Finder findFittedBox() {
|
|
return find.descendant(of: findStatic(), matching: find.byType(FittedBox));
|
|
}
|
|
|
|
Finder findStaticDefaultPreview() {
|
|
return find.descendant(of: findFittedBox(), matching: find.byType(ClipRSuperellipse));
|
|
}
|
|
|
|
group('CupertinoContextMenu before and during opening', () {
|
|
testWidgets('An unopened CupertinoContextMenu renders child in the same place as without', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Measure the child in the scene with no CupertinoContextMenu.
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(child: Center(child: child)),
|
|
),
|
|
);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
|
|
// When wrapped in a CupertinoContextMenu, the child is rendered in the same Rect.
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
expect(tester.getRect(find.byWidget(child)), childRect);
|
|
});
|
|
|
|
testWidgets('Can open CupertinoContextMenu by tap and hold', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsNothing,
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
Rect decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales by _kOpenSize.
|
|
await tester.pump(const Duration(milliseconds: 800));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kOpenScale);
|
|
|
|
// Then the CupertinoContextMenu opens.
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu is in the correct position when within a nested navigator', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800, 600)),
|
|
child: Align(
|
|
alignment: Alignment.bottomRight,
|
|
child: SizedBox(
|
|
width: 700,
|
|
height: 500,
|
|
child: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return CupertinoPageRoute<void>(
|
|
builder: (BuildContext context) => Align(
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction')),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsNothing,
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
Rect decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales by _kOpenSize.
|
|
await tester.pump(const Duration(milliseconds: 800));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kOpenScale);
|
|
|
|
// Then the CupertinoContextMenu opens.
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('_DecoyChild preserves the child color', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
backgroundColor: CupertinoColors.black,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800, 600)),
|
|
child: Center(
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction')),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Expect no _DecoyChild to be present before the gesture.
|
|
final Finder decoyChild = find.byWidgetPredicate(
|
|
(Widget w) => '${w.runtimeType}' == '_DecoyChild',
|
|
);
|
|
expect(decoyChild, findsNothing);
|
|
|
|
// Start press gesture on the child.
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// Find the _DecoyChild by runtimeType,
|
|
// find the Container descendant with the BoxDecoration,
|
|
// then read the boxDecoration property.
|
|
final Finder decoyChildDescendant = find.descendant(
|
|
of: decoyChild,
|
|
matching: find.byType(Container),
|
|
);
|
|
final boxDecoration =
|
|
(tester.firstWidget(decoyChildDescendant) as Container).decoration as BoxDecoration?;
|
|
const expectedColors = <Color?>[null, Color(0x00000000)];
|
|
|
|
// `Color(0x00000000)` -> Is `CupertinoColors.transparent`.
|
|
// `null` -> Default when no color argument is given in `BoxDecoration`.
|
|
// Any other color won't preserve the child's property.
|
|
expect(expectedColors, contains(boxDecoration?.color));
|
|
|
|
// End the gesture.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Expect no _DecoyChild to be present after ending the gesture.
|
|
final Finder decoyChildAfterEnding = find.byWidgetPredicate(
|
|
(Widget w) => '${w.runtimeType}' == '_DecoyChild',
|
|
);
|
|
expect(decoyChildAfterEnding, findsNothing);
|
|
});
|
|
|
|
testWidgets(
|
|
'CupertinoContextMenu with a basic builder opens and closes the same as when providing a child',
|
|
(WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
getBuilderContextMenu(
|
|
builder: (BuildContext context, Animation<double> animation) {
|
|
return child;
|
|
},
|
|
),
|
|
);
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsNothing,
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
Rect decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales by _kOpenSize.
|
|
await tester.pump(const Duration(milliseconds: 800));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kOpenScale);
|
|
|
|
// Then the CupertinoContextMenu opens.
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('CupertinoContextMenu with a builder can change the animation', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
getBuilderContextMenu(
|
|
builder: (BuildContext context, Animation<double> animation) {
|
|
return Container(
|
|
width: 300.0,
|
|
height: 100.0,
|
|
decoration: BoxDecoration(
|
|
color: CupertinoColors.activeOrange,
|
|
borderRadius: BorderRadius.circular(25.0 * animation.value),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
final Widget child = find
|
|
.descendant(of: find.byType(TickerMode), matching: find.byType(Container))
|
|
.evaluate()
|
|
.single
|
|
.widget;
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsNothing,
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
Finder findBuilderDecoyChild() {
|
|
return find.descendant(
|
|
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
matching: find.byType(Container),
|
|
);
|
|
}
|
|
|
|
final decoyContainer = tester.firstElement(findBuilderDecoyChild()).widget as Container;
|
|
final decoyDecoration = decoyContainer.decoration as BoxDecoration?;
|
|
expect(decoyDecoration?.borderRadius, equals(BorderRadius.circular(0)));
|
|
|
|
expect(findBuilderDecoyChild(), findsOneWidget);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate with a different border radius.
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
final decoyLaterContainer = tester.firstElement(findBuilderDecoyChild()).widget as Container;
|
|
final decoyLaterDecoration = decoyLaterContainer.decoration as BoxDecoration?;
|
|
expect(decoyLaterDecoration?.borderRadius, isNot(equals(BorderRadius.circular(0))));
|
|
|
|
// Finish gesture to release resources.
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Hovering over Cupertino context menu updates cursor to clickable on Web', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: Center(
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction One')),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
await gesture.addPointer(location: const Offset(10, 10));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.basic,
|
|
);
|
|
|
|
final Offset contextMenu = tester.getCenter(find.byWidget(child));
|
|
await gesture.moveTo(contextMenu);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
|
);
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu is in the correct position when within a Transform.scale', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800, 600)),
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: Align(
|
|
//alignment: Alignment.bottomRight,
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction')),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsNothing,
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
Rect decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales by _kOpenSize.
|
|
await tester.pump(const Duration(milliseconds: 800));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kOpenScale);
|
|
|
|
// Then the CupertinoContextMenu opens.
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
group('CupertinoContextMenu when open', () {
|
|
testWidgets('Last action does not have border', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: Center(
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction One')),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the CupertinoContextMenu
|
|
final TestGesture firstGesture = await tester.startGesture(
|
|
tester.getCenter(find.byWidget(child)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await firstGesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// Both the background color and the action colors are found.
|
|
expect(findStaticChildColor(tester), findsNWidgets(2));
|
|
|
|
// Close the CupertinoContextMenu.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: Center(
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction One')),
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction Two')),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the CupertinoContextMenu
|
|
final TestGesture secondGesture = await tester.startGesture(
|
|
tester.getCenter(find.byWidget(child)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await secondGesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
expect(findStaticChildColor(tester), findsNWidgets(3));
|
|
});
|
|
|
|
testWidgets('Can close CupertinoContextMenu by background tap', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
|
|
// Open the CupertinoContextMenu
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// Tap and ensure that the CupertinoContextMenu is closed.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
});
|
|
|
|
testWidgets('Can close CupertinoContextMenu by dragging down', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
|
|
// Open the CupertinoContextMenu
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// Drag down not far enough and it bounces back and doesn't close.
|
|
expect(findStaticChild(child), findsOneWidget);
|
|
Offset staticChildCenter = tester.getCenter(findStaticChild(child));
|
|
TestGesture swipeGesture = await tester.startGesture(staticChildCenter);
|
|
await swipeGesture.moveBy(
|
|
const Offset(0.0, 100.0),
|
|
timeStamp: const Duration(milliseconds: 100),
|
|
);
|
|
await tester.pump();
|
|
await swipeGesture.up();
|
|
await tester.pump();
|
|
expect(tester.getCenter(findStaticChild(child)).dy, greaterThan(staticChildCenter.dy));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.getCenter(findStaticChild(child)), equals(staticChildCenter));
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// Drag down far enough and it does close.
|
|
expect(findStaticChild(child), findsOneWidget);
|
|
staticChildCenter = tester.getCenter(findStaticChild(child));
|
|
swipeGesture = await tester.startGesture(staticChildCenter);
|
|
await swipeGesture.moveBy(
|
|
const Offset(0.0, 200.0),
|
|
timeStamp: const Duration(milliseconds: 100),
|
|
);
|
|
await tester.pump();
|
|
await swipeGesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
});
|
|
|
|
testWidgets('Can close CupertinoContextMenu by flinging down', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
|
|
// Open the CupertinoContextMenu
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// Fling up and nothing happens.
|
|
expect(findStaticChild(child), findsOneWidget);
|
|
await tester.fling(findStaticChild(child), const Offset(0.0, -100.0), 1000.0);
|
|
await tester.pumpAndSettle();
|
|
expect(findStaticChild(child), findsOneWidget);
|
|
|
|
// Fling down to close the menu.
|
|
expect(findStaticChild(child), findsOneWidget);
|
|
await tester.fling(findStaticChild(child), const Offset(0.0, 100.0), 1000.0);
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
});
|
|
|
|
testWidgets("Backdrop is added using ModalRoute's filter parameter", (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
expect(find.byType(BackdropFilter), findsNothing);
|
|
|
|
// Open the CupertinoContextMenu
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
expect(find.byType(BackdropFilter), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Preview widget should have the correct border radius', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
|
|
// Open the CupertinoContextMenu.
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// Check border radius.
|
|
expect(findStaticDefaultPreview(), findsOneWidget);
|
|
final previewWidget = tester.firstWidget(findStaticDefaultPreview()) as ClipRSuperellipse;
|
|
expect(previewWidget.borderRadius, equals(BorderRadius.circular(12.0)));
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu width is correct', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsNothing,
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
Rect decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales by _kOpenSize.
|
|
await tester.pump(const Duration(milliseconds: 800));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kOpenScale);
|
|
|
|
// Then the CupertinoContextMenu opens.
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
// The CupertinoContextMenu has the correct width and height.
|
|
final CupertinoContextMenu widget = tester.widget(find.byType(CupertinoContextMenu));
|
|
for (final Widget action in widget.actions) {
|
|
// The value of the height is 80 because of the font and icon size.
|
|
expect(tester.getSize(find.byWidget(action)).width, 250);
|
|
}
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu minimizes scaling offscreen', (WidgetTester tester) async {
|
|
const portraitScreenSize = Size(600.0, 800.0);
|
|
await binding.setSurfaceSize(portraitScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
final Widget child = getChild();
|
|
|
|
// Pump a CupertinoContextMenu on the top-left of the screen and open it.
|
|
await tester.pumpWidget(getContextMenu(alignment: Alignment.topLeft, child: child));
|
|
await tester.pump();
|
|
Rect childRect = tester.getRect(find.byWidget(child));
|
|
// Start a press on the child.
|
|
final TestGesture gesture1 = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
Rect decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales. Since the context menu is fully
|
|
// top-left aligned, the minimum scale factor is used so that the menu
|
|
// animates minimally off the screen.
|
|
await tester.pump(const Duration(milliseconds: 900));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kMinScaleFactor);
|
|
|
|
// Open and then close the CupertinoContextMenu.
|
|
await tester.pumpAndSettle();
|
|
await tester.tapAt(const Offset(599.0, 799.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
|
|
// Pump a CupertinoContextMenu on the bottom-right of the screen and open it.
|
|
await tester.pumpWidget(getContextMenu(alignment: Alignment.bottomRight, child: child));
|
|
await tester.pump();
|
|
childRect = tester.getRect(find.byWidget(child));
|
|
// Start a press on the child.
|
|
final TestGesture gesture2 = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
|
|
// The _DecoyChild is showing directly on top of the child.
|
|
expect(findDecoyChild(child), findsOneWidget);
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, equals(decoyChildRect));
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// After a small delay, the _DecoyChild has begun to animate.
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
|
|
// Eventually the decoy fully scales. Since the context menu is fully
|
|
// bottom-right aligned, the minimum scale factor is used so that the menu
|
|
// animates minimally off the screen.
|
|
await tester.pump(const Duration(milliseconds: 900));
|
|
decoyChildRect = tester.getRect(findDecoyChild(child));
|
|
expect(childRect, isNot(equals(decoyChildRect)));
|
|
expect(decoyChildRect.width, childRect.width * kMinScaleFactor);
|
|
|
|
// Open and then close the CupertinoContextMenu.
|
|
await tester.pumpAndSettle();
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
await gesture1.up();
|
|
await gesture2.up();
|
|
});
|
|
|
|
testWidgets("ContextMenu route animation doesn't throw exception on dismiss", (
|
|
WidgetTester tester,
|
|
) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/124597.
|
|
final List<int> items = List<int>.generate(2, (int index) => index).toList();
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return ListView(
|
|
children: items
|
|
.map(
|
|
(int index) => CupertinoContextMenu(
|
|
actions: <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(
|
|
child: const Text('DELETE'),
|
|
onPressed: () {
|
|
setState(() {
|
|
items.remove(index);
|
|
Navigator.of(context).pop();
|
|
});
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
],
|
|
child: Text('Item $index'),
|
|
),
|
|
)
|
|
.toList(),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the CupertinoContextMenu.
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Item 1')));
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Tap the delete action.
|
|
await tester.tap(find.text('DELETE'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The CupertinoContextMenu should be closed with no exception.
|
|
expect(find.text('DELETE'), findsNothing);
|
|
expect(tester.takeException(), null);
|
|
});
|
|
});
|
|
|
|
group("Open layout differs depending on child's position on screen", () {
|
|
testWidgets('Portrait', (WidgetTester tester) async {
|
|
const portraitScreenSize = Size(600.0, 800.0);
|
|
await binding.setSurfaceSize(portraitScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
// Pump a CupertinoContextMenu in the center of the screen and open it.
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(screenSize: portraitScreenSize, child: child));
|
|
expect(find.byType(CupertinoContextMenuAction), findsNothing);
|
|
Rect childRect = tester.getRect(find.byWidget(child));
|
|
TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// The position of the action is in the center of the screen.
|
|
expect(find.byType(CupertinoContextMenuAction), findsOneWidget);
|
|
final Offset center = tester.getTopLeft(find.byType(CupertinoContextMenuAction));
|
|
|
|
// Close the CupertinoContextMenu.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
|
|
// Pump a CupertinoContextMenu on the left of the screen and open it.
|
|
await tester.pumpWidget(
|
|
getContextMenu(
|
|
alignment: Alignment.centerLeft,
|
|
screenSize: portraitScreenSize,
|
|
child: child,
|
|
),
|
|
);
|
|
expect(find.byType(CupertinoContextMenuAction), findsNothing);
|
|
await tester.pumpAndSettle();
|
|
childRect = tester.getRect(find.byWidget(child));
|
|
gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// The position of the action is on the left of the screen.
|
|
expect(find.byType(CupertinoContextMenuAction), findsOneWidget);
|
|
final Offset left = tester.getTopLeft(find.byType(CupertinoContextMenuAction));
|
|
expect(left.dx, lessThan(center.dx));
|
|
|
|
// Close the CupertinoContextMenu.
|
|
await tester.tapAt(const Offset(559.0, 799.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
|
|
// Pump a CupertinoContextMenu on the right of the screen and open it.
|
|
await tester.pumpWidget(
|
|
getContextMenu(
|
|
alignment: Alignment.centerRight,
|
|
screenSize: portraitScreenSize,
|
|
child: child,
|
|
),
|
|
);
|
|
expect(find.byType(CupertinoContextMenuAction), findsNothing);
|
|
childRect = tester.getRect(find.byWidget(child));
|
|
gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// The position of the action is on the right of the screen.
|
|
expect(find.byType(CupertinoContextMenuAction), findsOneWidget);
|
|
final Offset right = tester.getTopLeft(find.byType(CupertinoContextMenuAction));
|
|
expect(right.dx, greaterThan(center.dx));
|
|
});
|
|
|
|
testWidgets('Landscape', (WidgetTester tester) async {
|
|
// Pump a CupertinoContextMenu in the center of the screen and open it.
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(getContextMenu(child: child));
|
|
expect(find.byType(CupertinoContextMenuAction), findsNothing);
|
|
Rect childRect = tester.getRect(find.byWidget(child));
|
|
TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Landscape doesn't support a centered action list, so the action is on
|
|
// the left side of the screen.
|
|
expect(find.byType(CupertinoContextMenuAction), findsOneWidget);
|
|
final Offset center = tester.getTopLeft(find.byType(CupertinoContextMenuAction));
|
|
|
|
// Close the CupertinoContextMenu.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
|
|
// Pump a CupertinoContextMenu on the left of the screen and open it.
|
|
await tester.pumpWidget(getContextMenu(alignment: Alignment.centerLeft, child: child));
|
|
expect(find.byType(CupertinoContextMenuAction), findsNothing);
|
|
childRect = tester.getRect(find.byWidget(child));
|
|
gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// The position of the action is on the right of the screen, which is the
|
|
// same as for center aligned children in landscape.
|
|
expect(find.byType(CupertinoContextMenuAction), findsOneWidget);
|
|
final Offset left = tester.getTopLeft(find.byType(CupertinoContextMenuAction));
|
|
expect(left.dx, equals(center.dx));
|
|
|
|
// Close the CupertinoContextMenu.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
|
|
// Pump a CupertinoContextMenu on the right of the screen and open it.
|
|
await tester.pumpWidget(getContextMenu(alignment: Alignment.centerRight, child: child));
|
|
expect(find.byType(CupertinoContextMenuAction), findsNothing);
|
|
childRect = tester.getRect(find.byWidget(child));
|
|
gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// The position of the action is on the left of the screen.
|
|
expect(find.byType(CupertinoContextMenuAction), findsOneWidget);
|
|
final Offset right = tester.getTopLeft(find.byType(CupertinoContextMenuAction));
|
|
expect(right.dx, lessThan(left.dx));
|
|
});
|
|
});
|
|
|
|
testWidgets('Conflicting gesture detectors', (WidgetTester tester) async {
|
|
int? onPointerDownTime;
|
|
int? onPointerUpTime;
|
|
var insideTapTriggered = false;
|
|
// The required duration of the route to be pushed in is [500, 900]ms.
|
|
// 500ms is calculated from kPressTimeout+_previewLongPressTimeout/2.
|
|
// 900ms is calculated from kPressTimeout+_previewLongPressTimeout.
|
|
const pressDuration = Duration(milliseconds: 501);
|
|
|
|
int now() => clock.now().millisecondsSinceEpoch;
|
|
|
|
await tester.pumpWidget(
|
|
Listener(
|
|
onPointerDown: (PointerDownEvent event) => onPointerDownTime = now(),
|
|
onPointerUp: (PointerUpEvent event) => onPointerUpTime = now(),
|
|
child: CupertinoApp(
|
|
home: Align(
|
|
child: CupertinoContextMenu(
|
|
actions: const <CupertinoContextMenuAction>[
|
|
CupertinoContextMenuAction(child: Text('CupertinoContextMenuAction')),
|
|
],
|
|
child: GestureDetector(
|
|
onTap: () => insideTapTriggered = true,
|
|
child: Container(
|
|
width: 200,
|
|
height: 200,
|
|
key: const Key('container'),
|
|
color: const Color(0xFF00FF00),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.createGesture();
|
|
await gesture.down(tester.getCenter(find.byKey(const Key('container'))));
|
|
// Simulate the actual situation:
|
|
// the user keeps pressing and requesting frames.
|
|
// If there is only one frame,
|
|
// the animation is mutant and cannot drive the value of the animation controller.
|
|
for (var i = 0; i < 100; i++) {
|
|
await tester.pump(pressDuration ~/ 100);
|
|
}
|
|
await gesture.up();
|
|
// Await pushing route.
|
|
await tester.pumpAndSettle();
|
|
|
|
// Judge whether _ContextMenuRouteStatic present on the screen.
|
|
final Finder routeStatic = find.byWidgetPredicate(
|
|
(Widget w) => '${w.runtimeType}' == '_ContextMenuRouteStatic',
|
|
);
|
|
|
|
// The insideTap and the route should not be triggered at the same time.
|
|
if (insideTapTriggered) {
|
|
// Calculate the actual duration.
|
|
final int actualDuration = onPointerUpTime! - onPointerDownTime!;
|
|
|
|
expect(
|
|
routeStatic,
|
|
findsNothing,
|
|
reason:
|
|
'When actualDuration($actualDuration) is in the range of 500ms~900ms, '
|
|
'which means the route is pushed, '
|
|
'but insideTap should not be triggered at the same time.',
|
|
);
|
|
} else {
|
|
// The route should be pushed when the insideTap is not triggered.
|
|
expect(routeStatic, findsOneWidget);
|
|
}
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu scrolls correctly', (WidgetTester tester) async {
|
|
const numMenuItems = 100;
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(100, 100)),
|
|
child: CupertinoContextMenu(
|
|
actions: List<CupertinoContextMenuAction>.generate(numMenuItems, (int index) {
|
|
return CupertinoContextMenuAction(child: Text('Item $index'), onPressed: () {});
|
|
}),
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the CupertinoContextMenu.
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byWidget(child)));
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(CupertinoContextMenu), findsOneWidget);
|
|
|
|
// Verify the first items are visible.
|
|
expect(find.text('Item 0'), findsOneWidget);
|
|
expect(find.text('Item 1'), findsOneWidget);
|
|
|
|
// Find the scrollable part of the context menu.
|
|
final Finder scrollableFinder = find.byType(Scrollable);
|
|
expect(scrollableFinder, findsOneWidget);
|
|
|
|
// Verify a scrollbar is displayed.
|
|
expect(find.byType(CupertinoScrollbar), findsOneWidget);
|
|
|
|
// Scroll to the bottom.
|
|
await tester.drag(scrollableFinder, const Offset(0, -500));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify the last item is visible.
|
|
expect(find.text('Item ${numMenuItems - 1}'), findsOneWidget);
|
|
|
|
// Scroll back to the top.
|
|
await tester.drag(scrollableFinder, const Offset(0, 500));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify the first items are still visible.
|
|
expect(find.text('Item 0'), findsOneWidget);
|
|
expect(find.text('Item 1'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Pushing a new route removes overlay', (WidgetTester tester) async {
|
|
final Widget child = getChild();
|
|
const page = 'Page 2';
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Builder(
|
|
builder: (BuildContext context) {
|
|
return Center(
|
|
child: CupertinoContextMenu(
|
|
actions: const <Widget>[CupertinoContextMenuAction(child: Text('Test'))],
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
Navigator.of(context).push(
|
|
CupertinoPageRoute<Widget>(
|
|
builder: (BuildContext context) =>
|
|
const CupertinoPageScaffold(child: Text(page)),
|
|
),
|
|
);
|
|
},
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
expect(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DecoyChild'), findsNothing);
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
expect(find.text(page), findsNothing);
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
await gesture.up();
|
|
|
|
// Kickstart the route transition.
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
// As the transition starts, the overlay has been removed.
|
|
// Only the child transitioning out is shown.
|
|
expect(find.text(page), findsOneWidget);
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Removing context menu from widget tree removes overlay', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget child = getChild();
|
|
var ctxMenuRemoved = false;
|
|
late StateSetter setState;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter stateSetter) {
|
|
setState = stateSetter;
|
|
return Center(
|
|
child: ctxMenuRemoved
|
|
? const SizedBox()
|
|
: CupertinoContextMenu(
|
|
actions: <Widget>[
|
|
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
|
|
],
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
setState(() {
|
|
ctxMenuRemoved = true;
|
|
});
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byWidget(child), findsNothing);
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu goldens in portrait orientation', (WidgetTester tester) async {
|
|
const portraitScreenSize = Size(800.0, 900.0);
|
|
await binding.setSurfaceSize(portraitScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
final Widget leftChild = getChild(width: 200, height: 300);
|
|
final Widget rightChild = getChild(width: 200, height: 300);
|
|
final Widget centerChild = getChild(width: 200, height: 300);
|
|
final children = <Widget>[leftChild, centerChild, rightChild];
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: GridView.count(
|
|
crossAxisCount: 3,
|
|
children: children.map((Widget child) {
|
|
return CupertinoContextMenu(actions: getActions(), child: child);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
Future<void> expectGolden(String name, Widget child) async {
|
|
// Open the child's CupertinoContextMenu.
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
await expectLater(findStatic(), matchesGoldenFile('context_menu.portrait.$name.png'));
|
|
|
|
// Tap and ensure that the CupertinoContextMenu is closed.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
}
|
|
|
|
await expectGolden('left', leftChild);
|
|
await expectGolden('center', centerChild);
|
|
await expectGolden('right', rightChild);
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu goldens in landscape orientation', (WidgetTester tester) async {
|
|
const landscapeScreenSize = Size(800.0, 600.0);
|
|
await binding.setSurfaceSize(landscapeScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
final Widget leftChild = getChild(width: 200, height: 300);
|
|
final Widget rightChild = getChild(width: 200, height: 300);
|
|
final Widget centerChild = getChild(width: 200, height: 300);
|
|
final children = <Widget>[leftChild, centerChild, rightChild];
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: GridView.count(
|
|
crossAxisCount: 3,
|
|
children: children.map((Widget child) {
|
|
return CupertinoContextMenu(actions: getActions(), child: child);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
Future<void> expectGolden(String name, Widget child) async {
|
|
// Open the child's CupertinoContextMenu.
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
await expectLater(findStatic(), matchesGoldenFile('context_menu.landscape.$name.png'));
|
|
|
|
// Tap and ensure that the CupertinoContextMenu is closed.
|
|
await tester.tapAt(const Offset(1.0, 1.0));
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsNothing);
|
|
}
|
|
|
|
await expectGolden('left', leftChild);
|
|
await expectGolden('center', centerChild);
|
|
await expectGolden('right', rightChild);
|
|
});
|
|
|
|
group('CupertinoContextMenu sheet shrink animation alignment - ', () {
|
|
Future<void> testShrinkAlignment({
|
|
required WidgetTester tester,
|
|
required Alignment alignment,
|
|
required Size screenSize,
|
|
required AlignmentDirectional expectedAlignment,
|
|
}) async {
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
getContextMenu(alignment: alignment, screenSize: screenSize, child: child),
|
|
);
|
|
|
|
// Open the CupertinoContextMenu.
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
final TestGesture openGesture = await tester.startGesture(childRect.center);
|
|
await tester.pumpAndSettle();
|
|
await openGesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(findStatic(), findsOneWidget);
|
|
|
|
final Finder sheetFinder = find.byWidgetPredicate(
|
|
(Widget widget) => widget.runtimeType.toString() == '_ContextMenuSheet',
|
|
);
|
|
expect(sheetFinder, findsOneWidget);
|
|
final Rect initialSheetRect = tester.getRect(sheetFinder);
|
|
final Finder staticChildFinder = findStaticChild(child);
|
|
expect(staticChildFinder, findsOneWidget);
|
|
await tester.pump();
|
|
|
|
// Drag down enough to trigger the shrink animation.
|
|
await tester.fling(staticChildFinder, Offset(0.0, childRect.height / 2), 1000.0);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
|
|
// The sheet has shrunk.
|
|
expect(sheetFinder, findsOneWidget);
|
|
final Rect shrunkSheetRect = tester.getRect(sheetFinder);
|
|
expect(shrunkSheetRect.width, lessThan(initialSheetRect.width));
|
|
expect(shrunkSheetRect.height, lessThan(initialSheetRect.height));
|
|
|
|
// Verify alignment based on how the rect has shrunk.
|
|
switch (expectedAlignment) {
|
|
case AlignmentDirectional.topStart:
|
|
expect(
|
|
shrunkSheetRect.left,
|
|
moreOrLessEquals(initialSheetRect.left, epsilon: Tolerance.defaultTolerance.distance),
|
|
);
|
|
case AlignmentDirectional.topCenter:
|
|
expect(
|
|
shrunkSheetRect.center.dx,
|
|
moreOrLessEquals(
|
|
initialSheetRect.center.dx,
|
|
epsilon: Tolerance.defaultTolerance.distance,
|
|
),
|
|
);
|
|
case AlignmentDirectional.topEnd:
|
|
expect(
|
|
shrunkSheetRect.right,
|
|
moreOrLessEquals(initialSheetRect.right, epsilon: Tolerance.defaultTolerance.distance),
|
|
);
|
|
default:
|
|
fail('Unhandled alignment: $expectedAlignment');
|
|
}
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
testWidgets('Portrait', (WidgetTester tester) async {
|
|
const portraitScreenSize = Size(600.0, 800.0);
|
|
await binding.setSurfaceSize(portraitScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
await testShrinkAlignment(
|
|
tester: tester,
|
|
alignment: Alignment.centerLeft,
|
|
screenSize: portraitScreenSize,
|
|
expectedAlignment: AlignmentDirectional.topStart,
|
|
);
|
|
await testShrinkAlignment(
|
|
tester: tester,
|
|
alignment: Alignment.center,
|
|
screenSize: portraitScreenSize,
|
|
expectedAlignment: AlignmentDirectional.topCenter,
|
|
);
|
|
await testShrinkAlignment(
|
|
tester: tester,
|
|
alignment: Alignment.centerRight,
|
|
screenSize: portraitScreenSize,
|
|
expectedAlignment: AlignmentDirectional.topEnd,
|
|
);
|
|
});
|
|
|
|
testWidgets('Landscape', (WidgetTester tester) async {
|
|
const landscapeScreenSize = Size(800.0, 600.0);
|
|
await binding.setSurfaceSize(landscapeScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
await testShrinkAlignment(
|
|
tester: tester,
|
|
alignment: Alignment.centerLeft,
|
|
screenSize: landscapeScreenSize,
|
|
expectedAlignment: AlignmentDirectional.topStart,
|
|
);
|
|
await testShrinkAlignment(
|
|
tester: tester,
|
|
alignment: Alignment.center,
|
|
screenSize: landscapeScreenSize,
|
|
expectedAlignment: AlignmentDirectional.topStart,
|
|
);
|
|
await testShrinkAlignment(
|
|
tester: tester,
|
|
alignment: Alignment.centerRight,
|
|
screenSize: landscapeScreenSize,
|
|
expectedAlignment: AlignmentDirectional.topEnd,
|
|
);
|
|
});
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu respects available screen width - Portrait', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const portraitScreenSize = Size(300.0, 350.0);
|
|
await binding.setSurfaceSize(portraitScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
final Widget child = getChild();
|
|
await tester.pumpWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(size: portraitScreenSize),
|
|
child: CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoContextMenu(
|
|
actions: <Widget>[
|
|
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.takeException(), null);
|
|
|
|
// Verify the child width is constrained correctly.
|
|
expect(findStatic(), findsOneWidget);
|
|
final Size fittedBoxSize = tester.getSize(findFittedBox());
|
|
// availableWidth = 300.0 (screen width) - 2 * 20.0 (padding) = 260.0
|
|
expect(fittedBoxSize.width, 260.0);
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu respects available screen width - Landscape', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const landscapeScreenSize = Size(350.0, 300.0);
|
|
await binding.setSurfaceSize(landscapeScreenSize);
|
|
addTearDown(() => binding.setSurfaceSize(null));
|
|
|
|
final Widget child = getChild(width: 500);
|
|
await tester.pumpWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(size: landscapeScreenSize),
|
|
child: CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoContextMenu(
|
|
actions: <Widget>[
|
|
CupertinoContextMenuAction(child: const Text('Test'), onPressed: () {}),
|
|
],
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byWidget(child), findsOneWidget);
|
|
final Rect childRect = tester.getRect(find.byWidget(child));
|
|
|
|
// Start a press on the child.
|
|
final TestGesture gesture = await tester.startGesture(childRect.center);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.takeException(), null);
|
|
|
|
// Verify the child width is constrained correctly.
|
|
expect(findStatic(), findsOneWidget);
|
|
final Size fittedBoxSize = tester.getSize(findFittedBox());
|
|
// availableWidth = 350.0 (screen width) - 2 * 20.0 (padding) = 310.0
|
|
// availableWidthForChild = 310.0 - 250.0 (menu width) = 60.0
|
|
expect(fittedBoxSize.width, 60.0);
|
|
});
|
|
|
|
testWidgets('CupertinoContextMenu does not crash at zero area', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox.shrink(
|
|
child: CupertinoContextMenu(
|
|
actions: const <Widget>[Text('X'), Text('Y')],
|
|
child: const Text('Y'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(CupertinoContextMenu)), Size.zero);
|
|
});
|
|
}
|