如何验证小部件是否“离屏”

Fra*_*cca 4 testing dart flutter widget-test-flutter

赏金信息:如果出现以下情况,我会接受您的回答:

\n
    \n
  • 不是沿线的东西do this instead
  • \n
  • 代码示例大部分没有变化
  • \n
  • 产生成功的测试,而不仅仅是文档中的一些引用
  • \n
  • 不需要任何额外的包
  • \n
\n
\n
\n

[编辑:07/02/21] 在flutter 社区 上关注 Miyoyo#5957 ,\ndiscord@iapicca Convert widget position to global, get width height, add both, and see if the resulting bottom right position is on screen?并使用以下答案作为参考:

\n\n
\n

给出下面的代码示例(也可以在 dartpad 上运行

\n
import \'package:flutter_test/flutter_test.dart\';\nimport \'package:flutter/material.dart\';\n\nfinal _testKey = GlobalKey();\nconst _fabKey = ValueKey(\'fab\');\nfinal _onScreen = ValueNotifier<bool>(true);\n\nvoid main() => runApp(_myApp);\n\nconst _myApp = MaterialApp(\n  home: Scaffold(\n    body: MyStage(),\n    floatingActionButton: MyFAB(),\n  ),\n);\n\nclass MyFAB extends StatelessWidget {\n  const MyFAB() : super(key: const ValueKey(\'MyFAB\'));\n\n  @override\n  Widget build(BuildContext context) => FloatingActionButton(\n        key: _fabKey,\n        onPressed: () => _onScreen.value = !_onScreen.value,\n      );\n}\n\nclass MyStage extends StatelessWidget {\n  const MyStage() : super(key: const ValueKey(\'MyStage\'));\n\n  @override\n  Widget build(BuildContext context) => Stack(\n        children: [\n          ValueListenableBuilder(\n            child: FlutterLogo(\n              key: _testKey,\n            ),\n            valueListenable: _onScreen,\n            builder: (context, isOnStage, child) => AnimatedPositioned(\n              top: MediaQuery.of(context).size.height *\n                  (_onScreen.value ? .5 : -1),\n              child: child,\n              duration: const Duration(milliseconds: 100),\n            ),\n          ),\n        ],\n      );\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

我想测试的是小部件off screen\n这里是到目前为止的测试代码

\n
void main() {\n  testWidgets(\'...\', (tester) async {\n    await tester.pumpWidget(_myApp);\n    final rect = _testKey.currentContext.findRenderObject().paintBounds;\n\n    expect(tester.getSize(find.byKey(_testKey)), rect.size,\n        reason: \'size should match\');\n\n    final lowestPointBefore = rect.bottomRight.dy;\n    print(\'lowest point **BEFORE** $lowestPointBefore ${DateTime.now()}\');\n    expect(lowestPointBefore > .0, true, reason: \'should be on-screen\');\n\n    await tester.tap(find.byKey(_fabKey));\n    await tester.pump(const Duration(milliseconds: 300));\n    final lowestPointAfter =\n        _testKey.currentContext.findRenderObject().paintBounds.bottomRight.dy;\n\n    print(\'lowest point **AFTER** $lowestPointAfter ${DateTime.now()}\');\n    expect(lowestPointAfter > .0, false, reason: \'should be off-screen\');\n  });\n}\n\n\n
Run Code Online (Sandbox Code Playgroud)\n

和产生的日志

\n
00:03 +0: ...                                                                                                                                                                                               \nlowest point **BEFORE** 24.0 2021-02-07 16:28:08.715558\nlowest point **AFTER** 24.0 2021-02-07 16:28:08.850733\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1 EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK \xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following TestFailure object was thrown running a test:\n  Expected: <false>\n  Actual: <true>\n\nWhen the exception was thrown, this was the stack:\n#4      main.<anonymous closure> (file:///home/francesco/projects/issue/test/widget_test.dart:83:5)\n<asynchronous suspension>\n<asynchronous suspension>\n(elided one frame from package:stack_trace)\n...\n\nThis was caught by the test expectation on the following line:\n  file:///home/francesco/projects/issue/test/widget_test.dart line 83\nThe test description was:\n  ...\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\n00:03 +0 -1: ... [E]                                                                                                                                                                                        \n  Test failed. See exception logs above.\n  The test description was: ...\n  \n00:03 +0 -1: Some tests failed.                                            \n
Run Code Online (Sandbox Code Playgroud)\n

我不确定我的方法是否正确,打印中的时间表明我

\n
lowest point **BEFORE** 24.0 2021-02-07 16:28:08.715558\nlowest point **AFTER** 24.0 2021-02-07 16:28:08.850733\n
Run Code Online (Sandbox Code Playgroud)\n

建议我\n await tester.pumpAndSettle(Duration(milliseconds: 300));\n不做我认为的事情

\n

Par*_*ave 5

问题是:

  1. 我们试图找到 FlutterLogo 的矩形,但 FlutterLogo 矩形将保持不变,父 AnimatedPositioned 小部件的位置实际上正在发生变化。
  2. 即使我们现在开始检查 AnimatedPositioned PaintBounds,它仍然是相同的,因为我们没有改变宽度,而是改变了它本身的位置。

解决方案:

  1. 通过 topWidget 获取屏幕矩形,对我来说它是 Scaffold。(如果我们有不同的小部件,例如包含 FAB 按钮的 HomeScreen,我们只需要找到该矩形)
  2. 在单击之前,我会检查 fab 按钮是否在屏幕上
  3. 点击并泵送小部件并使其稳定下来。
  4. 搜索小部件矩形,它将超出屏幕,即在我们的例子中为 -600

在代码本身中添加了注释

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');
  });
Run Code Online (Sandbox Code Playgroud)

注意:从代码中替换 _ ,因为它从测试文件中隐藏了对象。

输出:

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)
Run Code Online (Sandbox Code Playgroud)