2

I have ListView widgets within a TabBarView to allow users to scroll through the contents of each tab. I would like to test some of the widgets which are off screen. I followed this to scroll the ListView to bring the relevant widget on screen:

How to find off-screen ListView child in widget tests?

This works fine until I place the ListView in a TabBarView, then it fails.

I've rebuilt the code without the ListView in a TabBarView and the test passes. Placing the ListView in a TabBarView causes the test to fail with:

'zero widgets with text "bbb" (ignoring offstage widgets)'

debugDumpApp() output shows that this is because there has been no scrolling.

My stateful widget:

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Scroll test'),
        ),
        body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return [
              SliverAppBar(
                expandedHeight: 100.0,
                bottom: TabBar(
                  controller: _tabController,
                  tabs: <Widget>[
                    Tab(
                      child: Text('Tab one'),
                    ),
                    Tab(
                      child: Text('Tab two'),
                    )
                  ],
                ),
              )
            ];
          },
          body: TabBarView(
            controller: _tabController,
            children: <Widget>[
              ListView(
                children: <Widget>[
                  Container(
                    height: 800.0,
                    child: Text('aaa'),
                  ),
                  Text('bbb')
                ],
              ),
              ListView(
                children: <Widget>[
                  Container(
                    height: 800.0,
                    child: Text('ccc'),
                  ),
                  Text('ddd')
                ],
              ),
            ],
          )
        )
    );
  }
}

My test:

void main() {
  Widget buildTestableWidget(Widget widget) {
    return new MediaQuery(
        data: new MediaQueryData(), child: new MaterialApp(home: widget));
  }

  Home home = Home();

  testWidgets('scroll test', (WidgetTester tester) async {
    await tester.pumpWidget(buildTestableWidget(home));

    expect(find.text('aaa'), findsOneWidget);
    expect(find.text('bbb'), findsNothing);

    final Offset point = tester.getCenter(find.text("aaa"));
    final gesture = await tester.startGesture(point);
    await gesture.moveBy(const Offset(0.0, -400.0));

    await tester.pump();

    expect(find.text('bbb'), findsOneWidget);
  });

}

Is this a bug or do I need to do something different to get TestGesture.moveBy to work in a TabBarView? Many thanks for your help.

Richard Thomas
  • 141
  • 1
  • 7

1 Answers1

3

I think this issue is something to do with the fact that the drag ends outside of the scrollable area, WidgetTester has a method called dragFrom which handles that scenario for you.

testWidgets('scroll test', (WidgetTester tester) async {
    await tester.pumpWidget(buildTestableWidget(home));

    expect(find.text('aaa'), findsOneWidget);
    expect(find.text('bbb'), findsNothing);

    final Offset point = tester.getCenter(find.text("aaa"));
    await tester.dragFrom(point, Offset(0.0, -400.0));

    await tester.pump();

    expect(find.text('bbb'), findsOneWidget);
  });
Jordan Davies
  • 9,925
  • 6
  • 40
  • 51
  • Thank you Jordan, that works nicely. Given your explanation, I just tried getting `moveBy` to work by breaking the gesture into 4 smaller consecutive 100 pixel movements but no luck! – Richard Thomas Jun 17 '19 at 15:43
  • 1
    I'm surprised that this works given `dragFrom`, under the hood, "basically" does the same `startGesture` and `moveBy` in the OP. The difference would be accounting for slop and a `gesture.up()` at the end. In any case, using `dragFrom` doesn't work for me when testing controls in a ListView under a TabBarView. I've had to revert to `await binding.setSurfaceSize(Size(600.0, 2000.0));`. Not ideal but allows me to move past this sticking point. – Tres Dec 06 '19 at 02:48