list_view_builder_test.dart 10.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9 10 11 12 13
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';

import 'test_widgets.dart';

void main() {
  testWidgets('ListView.builder mount/dismount smoke test', (WidgetTester tester) async {
14
    final List<int> callbackTracker = <int>[];
15 16 17 18 19

    // the root view is 800x600 in the test environment
    // so if our widget is 100 pixels tall, it should fit exactly 6 times.

    Widget builder() {
20
      return Directionality(
21
        textDirection: TextDirection.ltr,
22 23
        child: FlipWidget(
          left: ListView.builder(
24 25 26
            itemExtent: 100.0,
            itemBuilder: (BuildContext context, int index) {
              callbackTracker.add(index);
27 28
              return Container(
                key: ValueKey<int>(index),
29
                height: 100.0,
30
                child: Text('$index'),
31 32 33 34
              );
            },
          ),
          right: const Text('Not Today'),
35 36 37 38 39 40
        ),
      );
    }

    await tester.pumpWidget(builder());

41
    final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
42

43 44 45 46 47
    expect(callbackTracker, equals(<int>[
      0, 1, 2, 3, 4, 5, // visible in viewport
      6, 7, 8, // in caching area
    ]));
    check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
48 49 50 51 52 53 54 55 56 57 58

    callbackTracker.clear();
    testWidget.flip();
    await tester.pump();

    expect(callbackTracker, equals(<int>[]));

    callbackTracker.clear();
    testWidget.flip();
    await tester.pump();

59 60 61 62 63
    expect(callbackTracker, equals(<int>[
      0, 1, 2, 3, 4, 5,
      6, 7, 8, // in caching area
    ]));
    check(visible: <int>[0, 1, 2, 3, 4, 5], hidden: <int>[ 6, 7, 8]);
64 65 66
  });

  testWidgets('ListView.builder vertical', (WidgetTester tester) async {
67
    final List<int> callbackTracker = <int>[];
68 69 70 71 72

    // the root view is 800x600 in the test environment
    // so if our widget is 200 pixels tall, it should fit exactly 3 times.
    // but if we are offset by 300 pixels, there will be 4, numbered 1-4.

73
    final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
74
      callbackTracker.add(index);
75 76
      return Container(
        key: ValueKey<int>(index),
77 78
        width: 500.0, // this should be ignored
        height: 400.0, // should be overridden by itemExtent
79
        child: Text('$index', textDirection: TextDirection.ltr),
80 81 82
      );
    };

83
    Widget buildWidget() {
84
      return Directionality(
85
        textDirection: TextDirection.ltr,
86 87 88
        child: FlipWidget(
          left: ListView.builder(
            controller: ScrollController(initialScrollOffset: 300.0),
89 90 91
            itemExtent: 200.0,
            itemBuilder: itemBuilder,
          ),
Ian Hickson's avatar
Ian Hickson committed
92
          right: const Text('Not Today'),
93 94 95 96 97
        ),
      );
    }

    void jumpTo(double newScrollOffset) {
Adam Barth's avatar
Adam Barth committed
98
      final ScrollableState scrollable = tester.state(find.byType(Scrollable));
99 100 101 102 103
      scrollable.position.jumpTo(newScrollOffset);
    }

    await tester.pumpWidget(buildWidget());

104 105 106 107 108 109
    expect(callbackTracker, equals(<int>[
      0, // in caching area
      1, 2, 3, 4,
      5, // in caching area
    ]));
    check(visible: <int>[1, 2, 3, 4], hidden: <int>[0, 5]);
110 111 112 113 114 115 116
    callbackTracker.clear();

    jumpTo(400.0);
    // now only 3 should fit, numbered 2-4.

    await tester.pumpWidget(buildWidget());

117 118 119 120 121 122
    expect(callbackTracker, equals(<int>[
      0, 1, // in caching area
      2, 3, 4,
      5, 6, // in caching area
    ]));
    check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5, 6]);
123 124 125 126 127 128 129
    callbackTracker.clear();

    jumpTo(500.0);
    // now 4 should fit, numbered 2-5.

    await tester.pumpWidget(buildWidget());

130 131 132 133 134 135
    expect(callbackTracker, equals(<int>[
      0, 1, // in caching area
      2, 3, 4, 5,
      6, // in caching area
    ]));
    check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6]);
136 137 138 139
    callbackTracker.clear();
  });

  testWidgets('ListView.builder horizontal', (WidgetTester tester) async {
140
    final List<int> callbackTracker = <int>[];
141 142 143 144 145

    // the root view is 800x600 in the test environment
    // so if our widget is 200 pixels wide, it should fit exactly 4 times.
    // but if we are offset by 300 pixels, there will be 5, numbered 1-5.

146
    final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
147
      callbackTracker.add(index);
148 149
      return Container(
        key: ValueKey<int>(index),
150 151
        width: 400.0, // this should be overridden by itemExtent
        height: 500.0, // this should be ignored
152
        child: Text('$index'),
153 154 155
      );
    };

156
    Widget buildWidget() {
157
      return Directionality(
158
        textDirection: TextDirection.ltr,
159 160 161
        child: FlipWidget(
          left: ListView.builder(
            controller: ScrollController(initialScrollOffset: 300.0),
162 163 164 165 166
            itemBuilder: itemBuilder,
            itemExtent: 200.0,
            scrollDirection: Axis.horizontal,
          ),
          right: const Text('Not Today'),
167 168 169 170 171
        ),
      );
    }

    void jumpTo(double newScrollOffset) {
Adam Barth's avatar
Adam Barth committed
172
      final ScrollableState scrollable = tester.state(find.byType(Scrollable));
173 174 175 176 177
      scrollable.position.jumpTo(newScrollOffset);
    }

    await tester.pumpWidget(buildWidget());

178 179 180 181 182 183
    expect(callbackTracker, equals(<int>[
      0, // in caching area
      1, 2, 3, 4, 5,
      6, // in caching area
    ]));
    check(visible: <int>[1, 2, 3, 4, 5], hidden: <int>[0, 6]);
184 185 186 187 188 189 190
    callbackTracker.clear();

    jumpTo(400.0);
    // now only 4 should fit, numbered 2-5.

    await tester.pumpWidget(buildWidget());

191 192 193 194 195 196
    expect(callbackTracker, equals(<int>[
      0, 1, // in caching area
      2, 3, 4, 5,
      6, 7, // in caching area
    ]));
    check(visible: <int>[2, 3, 4, 5], hidden: <int>[0, 1, 6, 7]);
197 198 199 200 201 202 203
    callbackTracker.clear();

    jumpTo(500.0);
    // now only 5 should fit, numbered 2-6.

    await tester.pumpWidget(buildWidget());

204 205 206 207 208 209
    expect(callbackTracker, equals(<int>[
      0, 1, // in caching area
      2, 3, 4, 5, 6,
      7, // in caching area
    ]));
    check(visible: <int>[2, 3, 4, 5, 6], hidden: <int>[0, 1, 7]);
210 211 212 213
    callbackTracker.clear();
  });

  testWidgets('ListView.builder 10 items, 2-3 items visible', (WidgetTester tester) async {
214
    final List<int> callbackTracker = <int>[];
215 216 217 218 219

    // The root view is 800x600 in the test environment and our list
    // items are 300 tall. Scrolling should cause two or three items
    // to be built.

220
    final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
221
      callbackTracker.add(index);
222
      return Text('$index', key: ValueKey<int>(index), textDirection: TextDirection.ltr);
223 224
    };

225
    final Widget testWidget = Directionality(
226
      textDirection: TextDirection.ltr,
227
      child: ListView.builder(
228 229 230 231
        itemBuilder: itemBuilder,
        itemExtent: 300.0,
        itemCount: 10,
      ),
232 233 234
    );

    void jumpTo(double newScrollOffset) {
Adam Barth's avatar
Adam Barth committed
235
      final ScrollableState scrollable = tester.state(find.byType(Scrollable));
236 237 238 239
      scrollable.position.jumpTo(newScrollOffset);
    }

    await tester.pumpWidget(testWidget);
240 241
    expect(callbackTracker, equals(<int>[0, 1, 2]));
    check(visible: <int>[0, 1], hidden: <int>[2]);
242 243 244 245 246
    callbackTracker.clear();

    jumpTo(150.0);
    await tester.pump();

247 248
    expect(callbackTracker, equals(<int>[3]));
    check(visible: <int>[0, 1, 2], hidden: <int>[3]);
249 250 251 252 253
    callbackTracker.clear();

    jumpTo(600.0);
    await tester.pump();

254 255
    expect(callbackTracker, equals(<int>[4]));
    check(visible: <int>[2, 3], hidden: <int>[0, 1, 4]);
256 257 258 259 260
    callbackTracker.clear();

    jumpTo(750.0);
    await tester.pump();

261 262
    expect(callbackTracker, equals(<int>[5]));
    check(visible: <int>[2, 3, 4], hidden: <int>[0, 1, 5]);
263 264 265
    callbackTracker.clear();
  });

266 267
  testWidgets('ListView.separated', (WidgetTester tester) async {
    Widget buildFrame({ int itemCount }) {
268
      return Directionality(
269
        textDirection: TextDirection.ltr,
270
        child: ListView.separated(
271 272
          itemCount: itemCount,
          itemBuilder: (BuildContext context, int index) {
273
            return SizedBox(
274
              height: 100.0,
275
              child: Text('i$index'),
276 277 278
            );
          },
          separatorBuilder: (BuildContext context, int index) {
279
            return SizedBox(
280
              height: 10.0,
281
              child: Text('s$index'),
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
            );
          },
        ),
      );
    }

    await tester.pumpWidget(buildFrame(itemCount: 0));
    expect(find.text('i0'), findsNothing);
    expect(find.text('s0'), findsNothing);

    await tester.pumpWidget(buildFrame(itemCount: 1));
    expect(find.text('i0'), findsOneWidget);
    expect(find.text('s0'), findsNothing);

    await tester.pumpWidget(buildFrame(itemCount: 2));
    expect(find.text('i0'), findsOneWidget);
    expect(find.text('s0'), findsOneWidget);
    expect(find.text('i1'), findsOneWidget);
    expect(find.text('s1'), findsNothing);

    // ListView's height is 600, so items i0-i5 and s0-s4 fit.
    await tester.pumpWidget(buildFrame(itemCount: 25));
304
    for (final String s in <String>['i0', 's0', 'i1', 's1', 'i2', 's2', 'i3', 's3', 'i4', 's4', 'i5'])
305 306 307 308
      expect(find.text(s), findsOneWidget);
    expect(find.text('s5'), findsNothing);
    expect(find.text('i6'), findsNothing);
  });
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356


  testWidgets('ListView.separated uses correct semanticChildCount', (WidgetTester tester) async {
    Widget buildFrame({int itemCount}) {
      return Directionality(
        textDirection: TextDirection.ltr,
        child: ListView.separated(
          itemCount: itemCount,
          itemBuilder: (BuildContext context, int index) {
            return SizedBox(
              height: 100.0,
              child: Text('i$index'),
            );
          },
          separatorBuilder: (BuildContext context, int index) {
            return SizedBox(
              height: 10.0,
              child: Text('s$index'),
            );
          },
        ),
      );
    }

    Scrollable scrollable() {
      return tester.widget<Scrollable>(
        find.descendant(
          of: find.byType(ListView),
          matching: find.byType(Scrollable),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(itemCount: 0));
    expect(scrollable().semanticChildCount, 0);

    await tester.pumpWidget(buildFrame(itemCount: 1));
    expect(scrollable().semanticChildCount, 1);

    await tester.pumpWidget(buildFrame(itemCount: 2));
    expect(scrollable().semanticChildCount, 2);

    await tester.pumpWidget(buildFrame(itemCount: 3));
    expect(scrollable().semanticChildCount, 3);

    await tester.pumpWidget(buildFrame(itemCount: 4));
    expect(scrollable().semanticChildCount, 4);
  });
357
}
358

359
void check({ List<int> visible = const <int>[], List<int> hidden = const <int>[] }) {
360
  for (final int i in visible) {
361 362
    expect(find.text('$i'), findsOneWidget);
  }
363
  for (final int i in hidden) {
364 365 366
    expect(find.text('$i'), findsNothing);
  }
}