blob: 0f60374b47debc5b012915a6ce0bd6f2f0ff8099 [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:fake_async/fake_async.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class _MockAnimationController extends AnimationController {
_MockAnimationController()
: super(duration: const Duration(minutes: 1), vsync: const TestVSync());
int forwardCalls = 0;
int reverseCalls = 0;
@override
TickerFuture forward({double? from}) {
forwardCalls++;
return super.forward(from: from);
}
@override
TickerFuture reverse({double? from}) {
reverseCalls++;
return super.reverse(from: from);
}
}
void main() {
Future<T> runFakeAsync<T>(Future<T> Function(FakeAsync time) f) async {
return FakeAsync().run((FakeAsync time) async {
bool pump = true;
final Future<T> future = f(time).whenComplete(() => pump = false);
while (pump) {
time.flushMicrotasks();
}
return future;
});
}
group('Raw Magnifier', () {
testWidgets('should render with correct focal point and decoration',
(WidgetTester tester) async {
final Key appKey = UniqueKey();
const Size magnifierSize = Size(100, 100);
const Offset magnifierFocalPoint = Offset(50, 50);
const Offset magnifierPosition = Offset(200, 200);
const double magnificationScale = 2;
await tester.pumpWidget(MaterialApp(
key: appKey,
home: Container(
color: Colors.orange,
width: double.infinity,
height: double.infinity,
child: Stack(
children: <Widget>[
Positioned(
// Positioned so that it is right in the center of the magnifier
// focal point.
left: magnifierPosition.dx + magnifierFocalPoint.dx,
top: magnifierPosition.dy + magnifierFocalPoint.dy,
child: Container(
color: Colors.pink,
// Since it is the size of the magnifier but over its
// magnificationScale, it should take up the whole magnifier.
width: (magnifierSize.width * 1.5) / magnificationScale,
height: (magnifierSize.height * 1.5) / magnificationScale,
),
),
Positioned(
left: magnifierPosition.dx,
top: magnifierPosition.dy,
child: const RawMagnifier(
size: magnifierSize,
focalPointOffset: magnifierFocalPoint,
magnificationScale: magnificationScale,
decoration: MagnifierDecoration(shadows: <BoxShadow>[
BoxShadow(
spreadRadius: 10,
blurRadius: 10,
color: Colors.green,
offset: Offset(5, 5),
),
]),
),
),
],
),
)));
await tester.pumpAndSettle();
// Should look like an orange screen, with two pink boxes.
// One pink box is in the magnifier (so has a green shadow) and is double
// size (from magnification). Also, the magnifier should be slightly orange
// since it has opacity.
await expectLater(
find.byKey(appKey),
matchesGoldenFile('widgets.magnifier.styled.png'),
);
}, skip: kIsWeb); // [intended] Bdf does not display on web.
group('transition states', () {
final AnimationController animationController = AnimationController(
vsync: const TestVSync(), duration: const Duration(minutes: 2));
final MagnifierController magnifierController = MagnifierController();
tearDown(() {
animationController.value = 0;
magnifierController.hide();
magnifierController.removeFromOverlay();
});
testWidgets(
'should immediately remove from overlay on no animation controller',
(WidgetTester tester) async {
await runFakeAsync((FakeAsync async) async {
const RawMagnifier testMagnifier = RawMagnifier(
size: Size(100, 100),
);
await tester.pumpWidget(const MaterialApp(
home: Placeholder(),
));
final BuildContext context =
tester.firstElement(find.byType(Placeholder));
magnifierController.show(
context: context,
builder: (BuildContext context) => testMagnifier,
);
WidgetsBinding.instance.scheduleFrame();
await tester.pump();
expect(magnifierController.overlayEntry, isNot(isNull));
magnifierController.hide();
WidgetsBinding.instance.scheduleFrame();
await tester.pump();
expect(magnifierController.overlayEntry, isNull);
});
});
testWidgets('should update shown based on animation status',
(WidgetTester tester) async {
await runFakeAsync((FakeAsync async) async {
final MagnifierController magnifierController =
MagnifierController(animationController: animationController);
const RawMagnifier testMagnifier = RawMagnifier(
size: Size(100, 100),
);
await tester.pumpWidget(const MaterialApp(
home: Placeholder(),
));
final BuildContext context =
tester.firstElement(find.byType(Placeholder));
magnifierController.show(
context: context,
builder: (BuildContext context) => testMagnifier,
);
WidgetsBinding.instance.scheduleFrame();
await tester.pump();
// No time has passed, so the animation controller has not completed.
expect(magnifierController.animationController?.status,
AnimationStatus.forward);
expect(magnifierController.shown, true);
async.elapse(animationController.duration!);
await tester.pumpAndSettle();
expect(magnifierController.animationController?.status,
AnimationStatus.completed);
expect(magnifierController.shown, true);
magnifierController.hide();
WidgetsBinding.instance.scheduleFrame();
await tester.pump();
expect(magnifierController.animationController?.status,
AnimationStatus.reverse);
expect(magnifierController.shown, false);
async.elapse(animationController.duration!);
await tester.pumpAndSettle();
expect(magnifierController.animationController?.status,
AnimationStatus.dismissed);
expect(magnifierController.shown, false);
});
});
});
});
group('magnifier controller', () {
final MagnifierController magnifierController = MagnifierController();
tearDown(() {
magnifierController.removeFromOverlay();
});
group('show', () {
testWidgets('should insert below below widget', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Text('text'),
));
final BuildContext context = tester.firstElement(find.byType(Text));
final Widget fakeMagnifier = Placeholder(key: UniqueKey());
final Widget fakeBefore = Placeholder(key: UniqueKey());
final OverlayEntry fakeBeforeOverlayEntry =
OverlayEntry(builder: (_) => fakeBefore);
Overlay.of(context).insert(fakeBeforeOverlayEntry);
magnifierController.show(
context: context,
builder: (_) => fakeMagnifier,
below: fakeBeforeOverlayEntry);
WidgetsBinding.instance.scheduleFrame();
await tester.pumpAndSettle();
final Iterable<Element> allOverlayChildren = find
.descendant(
of: find.byType(Overlay), matching: find.byType(Placeholder))
.evaluate();
// Expect the magnifier to be the first child, even though it was inserted
// after the fakeBefore.
expect(allOverlayChildren.last.widget.key, fakeBefore.key);
expect(allOverlayChildren.first.widget.key, fakeMagnifier.key);
});
testWidgets('should insert newly built widget without animating out if overlay != null',
(WidgetTester tester) async {
await runFakeAsync((FakeAsync async) async {
final _MockAnimationController animationController =
_MockAnimationController();
const RawMagnifier testMagnifier = RawMagnifier(
size: Size(100, 100),
);
const RawMagnifier testMagnifier2 = RawMagnifier(
size: Size(100, 100),
);
await tester.pumpWidget(const MaterialApp(
home: Placeholder(),
));
final BuildContext context =
tester.firstElement(find.byType(Placeholder));
magnifierController.show(
context: context,
builder: (BuildContext context) => testMagnifier,
);
WidgetsBinding.instance.scheduleFrame();
await tester.pump();
async.elapse(animationController.duration!);
await tester.pumpAndSettle();
magnifierController.show(context: context, builder: (_) => testMagnifier2);
WidgetsBinding.instance.scheduleFrame();
await tester.pump();
expect(animationController.reverseCalls, 0,
reason:
'should not have called reverse on animation controller due to force remove');
expect(find.byWidget(testMagnifier2), findsOneWidget);
});
});
});
group('shift within bounds', () {
final List<Rect> boundsRects = <Rect>[
const Rect.fromLTRB(0, 0, 100, 100),
const Rect.fromLTRB(0, 0, 100, 100),
const Rect.fromLTRB(0, 0, 100, 100),
const Rect.fromLTRB(0, 0, 100, 100),
];
final List<Rect> inputRects = <Rect>[
const Rect.fromLTRB(-100, -100, -80, -80),
const Rect.fromLTRB(0, 0, 20, 20),
const Rect.fromLTRB(110, 0, 120, 10),
const Rect.fromLTRB(110, 110, 120, 120)
];
final List<Rect> outputRects = <Rect>[
const Rect.fromLTRB(0, 0, 20, 20),
const Rect.fromLTRB(0, 0, 20, 20),
const Rect.fromLTRB(90, 0, 100, 10),
const Rect.fromLTRB(90, 90, 100, 100)
];
for (int i = 0; i < boundsRects.length; i++) {
test(
'should shift ${inputRects[i]} to ${outputRects[i]} for bounds ${boundsRects[i]}',
() {
final Rect outputRect = MagnifierController.shiftWithinBounds(
bounds: boundsRects[i], rect: inputRects[i]);
expect(outputRect, outputRects[i]);
});
}
});
});
}