Issue
bounty info: I’ll accept your answer if:
- isn’t something along the line
do this instead
- the code sample is mostly unchanged
- produce successful test, not just some quote from docs
- doesn’t need any extra package
[edit : 07/02/21] following Miyoyo#5957 on flutter community on
discord@iapicca Convert widget position to global, get width height, add both, and see if the resulting bottom right position is on screen?
and using the following answers as reference:
given the code sample below (also runnable on dartpad)
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
final _testKey = GlobalKey();
const _fabKey = ValueKey('fab');
final _onScreen = ValueNotifier<bool>(true);
void main() => runApp(_myApp);
const _myApp = MaterialApp(
home: Scaffold(
body: MyStage(),
floatingActionButton: MyFAB(),
),
);
class MyFAB extends StatelessWidget {
const MyFAB() : super(key: const ValueKey('MyFAB'));
@override
Widget build(BuildContext context) => FloatingActionButton(
key: _fabKey,
onPressed: () => _onScreen.value = !_onScreen.value,
);
}
class MyStage extends StatelessWidget {
const MyStage() : super(key: const ValueKey('MyStage'));
@override
Widget build(BuildContext context) => Stack(
children: [
ValueListenableBuilder(
child: FlutterLogo(
key: _testKey,
),
valueListenable: _onScreen,
builder: (context, isOnStage, child) => AnimatedPositioned(
top: MediaQuery.of(context).size.height *
(_onScreen.value ? .5 : -1),
child: child,
duration: const Duration(milliseconds: 100),
),
),
],
);
}
I want to test is the widget is off screen
here’s the test code so far
void main() {
testWidgets('...', (tester) async {
await tester.pumpWidget(_myApp);
final rect = _testKey.currentContext.findRenderObject().paintBounds;
expect(tester.getSize(find.byKey(_testKey)), rect.size,
reason: 'size should match');
final lowestPointBefore = rect.bottomRight.dy;
print('lowest point **BEFORE** $lowestPointBefore ${DateTime.now()}');
expect(lowestPointBefore > .0, true, reason: 'should be on-screen');
await tester.tap(find.byKey(_fabKey));
await tester.pump(const Duration(milliseconds: 300));
final lowestPointAfter =
_testKey.currentContext.findRenderObject().paintBounds.bottomRight.dy;
print('lowest point **AFTER** $lowestPointAfter ${DateTime.now()}');
expect(lowestPointAfter > .0, false, reason: 'should be off-screen');
});
}
and the logs produced
00:03 +0: ...
lowest point **BEFORE** 24.0 2021-02-07 16:28:08.715558
lowest point **AFTER** 24.0 2021-02-07 16:28:08.850733
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test:
Expected: <false>
Actual: <true>
When the exception was thrown, this was the stack:
#4 main.<anonymous closure> (file:///home/francesco/projects/issue/test/widget_test.dart:83:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...
This was caught by the test expectation on the following line:
file:///home/francesco/projects/issue/test/widget_test.dart line 83
The test description was:
...
════════════════════════════════════════════════════════════════════════════════════════════════════
00:03 +0 -1: ... [E]
Test failed. See exception logs above.
The test description was: ...
00:03 +0 -1: Some tests failed.
I’m not sure if my approach is correct
and the time in the print suggest me that
lowest point **BEFORE** 24.0 2021-02-07 16:28:08.715558
lowest point **AFTER** 24.0 2021-02-07 16:28:08.850733
suggest me that
await tester.pumpAndSettle(Duration(milliseconds: 300));
doesn’t do what I think it does
Solution
Problems are:
- We were trying to find the rect of FlutterLogo but FlutterLogo rect will remain same the parent AnimatedPositioned widget’s location are actually changing.
- Even though we now start to check for AnimatedPositioned paintBounds it will still be the same as we are not changing width but the position it self.
Solution:
- Get the screen rect by topWidget for me it’s Scaffold. (if we have different widgets like HomeScreen which contains FAB button we just need to find that rect)
- Before click I’m checking if fab button is on-screen or not
- Tap and pump the widget and let it settle.
- Search for widget rect and it will be out of the screen i.e. in our case -600
Added comments in the code it self
testWidgets('...', (tester) async {
await tester.pumpWidget(MyApp);
//check screen width height - here I'm checking for scaffold but you can put some other logic for screen size or parent widget type
Rect screenRect = tester.getRect(find.byType(Scaffold));
print("screenRect: $screenRect");
//checking previous position of the widget - on our case we are animating widget position via AnimatedPositioned
// which in itself is a statefulwidget and has Positioned widget inside
//also if we have multiple widgets of same type give them uniqueKey
AnimatedPositioned widget =
tester.firstWidget(find.byType(AnimatedPositioned));
double topPosition = widget.top;
print(widget);
print("AnimatedPositioned topPosition: $topPosition}");
expect(
screenRect.bottom > topPosition && screenRect.top < topPosition, true,
reason: 'should be on-screen');
//click button to animate the widget and wait
await tester.tap(find.byKey(fabKey));
//this will wait for animation to settle or call pump after duration
await tester.pumpAndSettle(const Duration(milliseconds: 300));
//check after position of the widget
AnimatedPositioned afterAnimationWidget =
tester.firstWidget(find.byType(AnimatedPositioned));
double afterAnimationTopPosition = afterAnimationWidget.top;
Rect animatedWidgetRect = tester.getRect(find.byType(AnimatedPositioned));
print("rect of widget : $animatedWidgetRect");
expect(
screenRect.bottom > afterAnimationTopPosition &&
screenRect.top < afterAnimationTopPosition,
false,
reason: 'should be off-screen');
});
Note: replaced _ from code as it was hiding the object from test file.
Output:
screenRect: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)
fab clicked
rect of widget : Rect.fromLTRB(0.0, -600.0, 24.0, -576.0)
Answered By – Parth Dave
Answer Checked By – Gilberto Lyons (FlutterFixes Admin)