blob: e777c7f26963f475e118eff39b010da9749e506c [file] [log] [blame]
// 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'])
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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', () {
testWidgets(
'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(
Container(
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'),
);
});
testWidgets(
'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));
});
testWidgets(
'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));
});
testWidgets('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);
});
testWidgets('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;
}
testWidgets(
'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));
});
testWidgets(
'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;
}
testWidgets('should not be animated on the inital 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);
});
testWidgets('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);
});
testWidgets('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);
});
testWidgets('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);
});
});
});
}