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 5 6 7 8 9 10 11
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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 {
12
    final List<int> callbackTracker = <int>[];
13 14 15 16 17

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

    await tester.pumpWidget(builder());

39
    final FlipWidgetState testWidget = tester.state(find.byType(FlipWidget));
40

41 42 43 44 45
    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]);
46 47 48 49 50 51 52 53 54 55 56

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

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

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

57 58 59 60 61
    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]);
62 63 64
  });

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

    // 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.

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

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

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

    await tester.pumpWidget(buildWidget());

102 103 104 105 106 107
    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]);
108 109 110 111 112 113 114
    callbackTracker.clear();

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

    await tester.pumpWidget(buildWidget());

115 116 117 118 119 120
    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]);
121 122 123 124 125 126 127
    callbackTracker.clear();

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

    await tester.pumpWidget(buildWidget());

128 129 130 131 132 133
    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]);
134 135 136 137
    callbackTracker.clear();
  });

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

    // 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.

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

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

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

    await tester.pumpWidget(buildWidget());

176 177 178 179 180 181
    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]);
182 183 184 185 186 187 188
    callbackTracker.clear();

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

    await tester.pumpWidget(buildWidget());

189 190 191 192 193 194
    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]);
195 196 197 198 199 200 201
    callbackTracker.clear();

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

    await tester.pumpWidget(buildWidget());

202 203 204 205 206 207
    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]);
208 209 210 211
    callbackTracker.clear();
  });

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

    // 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.

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

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

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

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

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

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

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

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

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

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

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

    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));
302
    for (final String s in <String>['i0', 's0', 'i1', 's1', 'i2', 's2', 'i3', 's3', 'i4', 's4', 'i5'])
303 304 305 306
      expect(find.text(s), findsOneWidget);
    expect(find.text('s5'), findsNothing);
    expect(find.text('i6'), findsNothing);
  });
307 308 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


  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);
  });
355
}
356

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