
Reverts flutter/flutter#131998 Reverting for https://github.com/flutter/flutter/issues/132222
495 lines
17 KiB
Dart
495 lines
17 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.
|
|
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../foundation/leak_tracking.dart';
|
|
|
|
void main() {
|
|
final MagnifierController magnifierController = MagnifierController();
|
|
const Rect reasonableTextField = Rect.fromLTRB(50, 100, 200, 100);
|
|
final Offset basicOffset = Offset(Magnifier.kDefaultMagnifierSize.width / 2,
|
|
Magnifier.kStandardVerticalFocalPointShift + Magnifier.kDefaultMagnifierSize.height);
|
|
|
|
Offset getMagnifierPosition(WidgetTester tester, [bool animated = false]) {
|
|
if (animated) {
|
|
final AnimatedPositioned animatedPositioned =
|
|
tester.firstWidget(find.byType(AnimatedPositioned));
|
|
return Offset(animatedPositioned.left ?? 0, animatedPositioned.top ?? 0);
|
|
} else {
|
|
final Positioned positioned = tester.firstWidget(find.byType(Positioned));
|
|
return Offset(positioned.left ?? 0, positioned.top ?? 0);
|
|
}
|
|
}
|
|
|
|
Future<void> showMagnifier(
|
|
BuildContext context,
|
|
WidgetTester tester,
|
|
ValueNotifier<MagnifierInfo> magnifierInfo,
|
|
) async {
|
|
final Future<void> magnifierShown = magnifierController.show(
|
|
context: context,
|
|
builder: (_) => TextMagnifier(
|
|
magnifierInfo: magnifierInfo,
|
|
));
|
|
|
|
WidgetsBinding.instance.scheduleFrame();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that the magnifier is shown.
|
|
await magnifierShown;
|
|
}
|
|
|
|
tearDown(() {
|
|
magnifierController.removeFromOverlay();
|
|
magnifierController.animationController = null;
|
|
});
|
|
|
|
group('adaptiveMagnifierControllerBuilder', () {
|
|
testWidgets('should return a TextEditingMagnifier on Android',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context = tester.firstElement(find.byType(Placeholder));
|
|
|
|
final Widget? builtWidget = TextMagnifier.adaptiveMagnifierConfiguration.magnifierBuilder(
|
|
context,
|
|
MagnifierController(),
|
|
ValueNotifier<MagnifierInfo>(MagnifierInfo.empty),
|
|
);
|
|
|
|
expect(builtWidget, isA<TextMagnifier>());
|
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
|
|
|
testWidgets('should return a CupertinoMagnifier on iOS',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context = tester.firstElement(find.byType(Placeholder));
|
|
|
|
final Widget? builtWidget = TextMagnifier.adaptiveMagnifierConfiguration.magnifierBuilder(
|
|
context,
|
|
MagnifierController(),
|
|
ValueNotifier<MagnifierInfo>(MagnifierInfo.empty),
|
|
);
|
|
|
|
expect(builtWidget, isA<CupertinoTextMagnifier>());
|
|
}, variant: TargetPlatformVariant.only(TargetPlatform.iOS));
|
|
|
|
testWidgets('should return null on all platforms not Android, iOS',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context = tester.firstElement(find.byType(Placeholder));
|
|
|
|
final Widget? builtWidget = TextMagnifier.adaptiveMagnifierConfiguration.magnifierBuilder(
|
|
context,
|
|
MagnifierController(),
|
|
ValueNotifier<MagnifierInfo>(MagnifierInfo.empty),
|
|
);
|
|
|
|
expect(builtWidget, isNull);
|
|
},
|
|
variant: TargetPlatformVariant.all(
|
|
excluding: <TargetPlatform>{
|
|
TargetPlatform.iOS,
|
|
TargetPlatform.android
|
|
}),
|
|
);
|
|
});
|
|
|
|
group('magnifier', () {
|
|
group('position', () {
|
|
testWidgetsWithLeakTracking(
|
|
'should be at gesture position if does not violate any positioning rules',
|
|
(WidgetTester tester) async {
|
|
final Key textField = UniqueKey();
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
await tester.pumpWidget(
|
|
ColoredBox(
|
|
color: const Color.fromARGB(255, 0, 255, 179),
|
|
child: MaterialApp(
|
|
home: Center(
|
|
child: Container(
|
|
key: textField,
|
|
width: 10,
|
|
height: 10,
|
|
color: Colors.red,
|
|
child: const Placeholder(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
// Magnifier should be positioned directly over the red square.
|
|
final RenderBox tapPointRenderBox =
|
|
tester.firstRenderObject(find.byKey(textField)) as RenderBox;
|
|
final Rect fakeTextFieldRect =
|
|
tapPointRenderBox.localToGlobal(Offset.zero) &
|
|
tapPointRenderBox.size;
|
|
|
|
final ValueNotifier<MagnifierInfo> magnifierInfo =
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: fakeTextFieldRect,
|
|
fieldBounds: fakeTextFieldRect,
|
|
caretRect: fakeTextFieldRect,
|
|
// The tap position is dragBelow units below the text field.
|
|
globalGesturePosition: fakeTextFieldRect.center,
|
|
));
|
|
|
|
await showMagnifier(context, tester, magnifierInfo);
|
|
|
|
// Should show two red squares; original, and one in the magnifier,
|
|
// directly ontop of one another.
|
|
await expectLater(
|
|
find.byType(MaterialApp),
|
|
matchesGoldenFile('magnifier.position.default.png'),
|
|
);
|
|
});
|
|
|
|
testWidgetsWithLeakTracking(
|
|
'should never move outside the right bounds of the editing line',
|
|
(WidgetTester tester) async {
|
|
const double gestureOutsideLine = 100;
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
// Inflate these two to make sure we're bounding on the
|
|
// current line boundaries, not anything else.
|
|
fieldBounds: reasonableTextField.inflate(gestureOutsideLine),
|
|
caretRect: reasonableTextField.inflate(gestureOutsideLine),
|
|
// The tap position is far out of the right side of the app.
|
|
globalGesturePosition: Offset(reasonableTextField.right + gestureOutsideLine, 0),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Should be less than the right edge, since we have padding.
|
|
expect(getMagnifierPosition(tester).dx,
|
|
lessThanOrEqualTo(reasonableTextField.right));
|
|
});
|
|
|
|
testWidgetsWithLeakTracking(
|
|
'should never move outside the left bounds of the editing line',
|
|
(WidgetTester tester) async {
|
|
const double gestureOutsideLine = 100;
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
// Inflate these two to make sure we're bounding on the
|
|
// current line boundaries, not anything else.
|
|
fieldBounds: reasonableTextField.inflate(gestureOutsideLine),
|
|
caretRect: reasonableTextField.inflate(gestureOutsideLine),
|
|
// The tap position is far out of the left side of the app.
|
|
globalGesturePosition: Offset(reasonableTextField.left - gestureOutsideLine, 0),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(getMagnifierPosition(tester).dx + basicOffset.dx,
|
|
greaterThanOrEqualTo(reasonableTextField.left));
|
|
});
|
|
|
|
testWidgetsWithLeakTracking('should position vertically at the center of the line', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
globalGesturePosition: reasonableTextField.center,
|
|
)));
|
|
|
|
expect(getMagnifierPosition(tester).dy,
|
|
reasonableTextField.center.dy - basicOffset.dy);
|
|
});
|
|
|
|
testWidgetsWithLeakTracking('should reposition vertically if mashed against the ceiling',
|
|
(WidgetTester tester) async {
|
|
final Rect topOfScreenTextFieldRect =
|
|
Rect.fromPoints(Offset.zero, const Offset(200, 0));
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: topOfScreenTextFieldRect,
|
|
fieldBounds: topOfScreenTextFieldRect,
|
|
caretRect: topOfScreenTextFieldRect,
|
|
globalGesturePosition: topOfScreenTextFieldRect.topCenter,
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(getMagnifierPosition(tester).dy, greaterThanOrEqualTo(0));
|
|
});
|
|
});
|
|
|
|
group('focal point', () {
|
|
Offset getMagnifierAdditionalFocalPoint(WidgetTester tester) {
|
|
final Magnifier magnifier = tester.firstWidget(find.byType(Magnifier));
|
|
return magnifier.additionalFocalPointOffset;
|
|
}
|
|
|
|
testWidgetsWithLeakTracking(
|
|
'should shift focal point so that the lens sees nothing out of bounds',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
// Gesture on the far right of the magnifier.
|
|
globalGesturePosition: reasonableTextField.topLeft,
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(getMagnifierAdditionalFocalPoint(tester).dx,
|
|
lessThan(reasonableTextField.left));
|
|
});
|
|
|
|
testWidgetsWithLeakTracking(
|
|
'focal point should shift if mashed against the top to always point to text',
|
|
(WidgetTester tester) async {
|
|
final Rect topOfScreenTextFieldRect =
|
|
Rect.fromPoints(Offset.zero, const Offset(200, 0));
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: topOfScreenTextFieldRect,
|
|
fieldBounds: topOfScreenTextFieldRect,
|
|
caretRect: topOfScreenTextFieldRect,
|
|
globalGesturePosition: topOfScreenTextFieldRect.topCenter,
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(getMagnifierAdditionalFocalPoint(tester).dy, lessThan(0));
|
|
});
|
|
});
|
|
|
|
group('animation state', () {
|
|
bool getIsAnimated(WidgetTester tester) {
|
|
final AnimatedPositioned animatedPositioned =
|
|
tester.firstWidget(find.byType(AnimatedPositioned));
|
|
return animatedPositioned.duration.compareTo(Duration.zero) != 0;
|
|
}
|
|
|
|
testWidgetsWithLeakTracking('should not be animated on the initial state',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
await showMagnifier(
|
|
context,
|
|
tester,
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
globalGesturePosition: reasonableTextField.center,
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(getIsAnimated(tester), false);
|
|
});
|
|
|
|
testWidgetsWithLeakTracking('should not be animated on horizontal shifts',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
final ValueNotifier<MagnifierInfo> magnifierPositioner =
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
globalGesturePosition: reasonableTextField.center,
|
|
),
|
|
);
|
|
|
|
await showMagnifier(context, tester, magnifierPositioner);
|
|
|
|
// New position has a horizontal shift.
|
|
magnifierPositioner.value = MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
globalGesturePosition:
|
|
reasonableTextField.center + const Offset(200, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getIsAnimated(tester), false);
|
|
});
|
|
|
|
testWidgetsWithLeakTracking('should be animated on vertical shifts',
|
|
(WidgetTester tester) async {
|
|
const Offset verticalShift = Offset(0, 200);
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
final ValueNotifier<MagnifierInfo> magnifierPositioner =
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
globalGesturePosition: reasonableTextField.center,
|
|
),
|
|
);
|
|
|
|
await showMagnifier(context, tester, magnifierPositioner);
|
|
|
|
// New position has a vertical shift.
|
|
magnifierPositioner.value = MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField.shift(verticalShift),
|
|
fieldBounds: Rect.fromPoints(reasonableTextField.topLeft,
|
|
reasonableTextField.bottomRight + verticalShift),
|
|
caretRect: reasonableTextField.shift(verticalShift),
|
|
globalGesturePosition: reasonableTextField.center + verticalShift,
|
|
);
|
|
|
|
await tester.pump();
|
|
expect(getIsAnimated(tester), true);
|
|
});
|
|
|
|
testWidgetsWithLeakTracking('should stop being animated when timer is up',
|
|
(WidgetTester tester) async {
|
|
const Offset verticalShift = Offset(0, 200);
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Placeholder(),
|
|
));
|
|
|
|
final BuildContext context =
|
|
tester.firstElement(find.byType(Placeholder));
|
|
|
|
final ValueNotifier<MagnifierInfo> magnifierPositioner =
|
|
ValueNotifier<MagnifierInfo>(
|
|
MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField,
|
|
fieldBounds: reasonableTextField,
|
|
caretRect: reasonableTextField,
|
|
globalGesturePosition: reasonableTextField.center,
|
|
),
|
|
);
|
|
|
|
await showMagnifier(context, tester, magnifierPositioner);
|
|
|
|
// New position has a vertical shift.
|
|
magnifierPositioner.value = MagnifierInfo(
|
|
currentLineBoundaries: reasonableTextField.shift(verticalShift),
|
|
fieldBounds: Rect.fromPoints(reasonableTextField.topLeft,
|
|
reasonableTextField.bottomRight + verticalShift),
|
|
caretRect: reasonableTextField.shift(verticalShift),
|
|
globalGesturePosition: reasonableTextField.center + verticalShift,
|
|
);
|
|
|
|
await tester.pump();
|
|
expect(getIsAnimated(tester), true);
|
|
await tester.pump(TextMagnifier.jumpBetweenLinesAnimationDuration +
|
|
const Duration(seconds: 2));
|
|
expect(getIsAnimated(tester), false);
|
|
});
|
|
});
|
|
});
|
|
}
|