scroll_controller_test.dart 11.7 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 'states.dart';

void main() {
  testWidgets('ScrollController control test', (WidgetTester tester) async {
14
    final ScrollController controller = ScrollController();
15

16
    await tester.pumpWidget(
17
      Directionality(
18
        textDirection: TextDirection.ltr,
19
        child: ListView(
20 21
          controller: controller,
          children: kStates.map<Widget>((String state) {
22
            return Container(
23
              height: 200.0,
24
              child: Text(state),
25 26 27 28 29
            );
          }).toList(),
        ),
      ),
    );
30 31

    double realOffset() {
Adam Barth's avatar
Adam Barth committed
32
      return tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels;
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
    }

    expect(controller.offset, equals(0.0));
    expect(realOffset(), equals(controller.offset));

    controller.jumpTo(653.0);

    expect(controller.offset, equals(653.0));
    expect(realOffset(), equals(controller.offset));

    await tester.pump();

    expect(controller.offset, equals(653.0));
    expect(realOffset(), equals(controller.offset));

    controller.animateTo(326.0, duration: const Duration(milliseconds: 300), curve: Curves.ease);
49
    await tester.pumpAndSettle(const Duration(milliseconds: 100));
50 51 52 53

    expect(controller.offset, equals(326.0));
    expect(realOffset(), equals(controller.offset));

54
    await tester.pumpWidget(
55
      Directionality(
56
        textDirection: TextDirection.ltr,
57
        child: ListView(
58 59 60
          key: const Key('second'),
          controller: controller,
          children: kStates.map<Widget>((String state) {
61
            return Container(
62
              height: 200.0,
63
              child: Text(state),
64 65 66 67 68
            );
          }).toList(),
        ),
      ),
    );
69 70 71 72 73 74 75 76 77

    expect(controller.offset, equals(0.0));
    expect(realOffset(), equals(controller.offset));

    controller.jumpTo(653.0);

    expect(controller.offset, equals(653.0));
    expect(realOffset(), equals(controller.offset));

78
    final ScrollController controller2 = ScrollController();
79

80
    await tester.pumpWidget(
81
      Directionality(
82
        textDirection: TextDirection.ltr,
83
        child: ListView(
84 85 86
          key: const Key('second'),
          controller: controller2,
          children: kStates.map<Widget>((String state) {
87
            return Container(
88
              height: 200.0,
89
              child: Text(state),
90 91 92 93 94
            );
          }).toList(),
        ),
      ),
    );
95

96
    expect(() => controller.offset, throwsAssertionError);
97 98 99
    expect(controller2.offset, equals(653.0));
    expect(realOffset(), equals(controller2.offset));

100 101
    expect(() => controller.jumpTo(120.0), throwsAssertionError);
    expect(() => controller.animateTo(132.0, duration: const Duration(milliseconds: 300), curve: Curves.ease), throwsAssertionError);
102

103
    await tester.pumpWidget(
104
      Directionality(
105
        textDirection: TextDirection.ltr,
106
        child: ListView(
107 108 109 110
          key: const Key('second'),
          controller: controller2,
          physics: const BouncingScrollPhysics(),
          children: kStates.map<Widget>((String state) {
111
            return Container(
112
              height: 200.0,
113
              child: Text(state),
114 115 116 117 118
            );
          }).toList(),
        ),
      ),
    );
119 120 121 122 123 124 125 126 127 128 129 130 131 132

    expect(controller2.offset, equals(653.0));
    expect(realOffset(), equals(controller2.offset));

    controller2.jumpTo(432.0);

    expect(controller2.offset, equals(432.0));
    expect(realOffset(), equals(controller2.offset));

    await tester.pump();

    expect(controller2.offset, equals(432.0));
    expect(realOffset(), equals(controller2.offset));
  });
133 134

  testWidgets('ScrollController control test', (WidgetTester tester) async {
135
    final ScrollController controller = ScrollController(
136 137 138
      initialScrollOffset: 209.0,
    );

139
    await tester.pumpWidget(
140
      Directionality(
141
        textDirection: TextDirection.ltr,
142
        child: GridView.count(
143 144
          crossAxisCount: 4,
          controller: controller,
145
          children: kStates.map<Widget>((String state) => Text(state)).toList(),
146 147 148
        ),
      ),
    );
149 150

    double realOffset() {
Adam Barth's avatar
Adam Barth committed
151
      return tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels;
152 153 154 155 156 157 158 159 160 161 162 163
    }

    expect(controller.offset, equals(209.0));
    expect(realOffset(), equals(controller.offset));

    controller.jumpTo(105.0);

    await tester.pump();

    expect(controller.offset, equals(105.0));
    expect(realOffset(), equals(controller.offset));

164
    await tester.pumpWidget(
165
      Directionality(
166
        textDirection: TextDirection.ltr,
167
        child: GridView.count(
168 169
          crossAxisCount: 2,
          controller: controller,
170
          children: kStates.map<Widget>((String state) => Text(state)).toList(),
171 172 173
        ),
      ),
    );
174 175 176 177

    expect(controller.offset, equals(105.0));
    expect(realOffset(), equals(controller.offset));
  });
178 179

  testWidgets('DrivenScrollActivity ending after dispose', (WidgetTester tester) async {
180
    final ScrollController controller = ScrollController();
181

182
    await tester.pumpWidget(
183
      Directionality(
184
        textDirection: TextDirection.ltr,
185
        child: ListView(
186
          controller: controller,
187
          children: <Widget>[ Container(height: 200000.0) ],
188 189 190
        ),
      ),
    );
191 192 193 194 195 196

    controller.animateTo(1000.0, duration: const Duration(seconds: 1), curve: Curves.linear);

    await tester.pump(); // Start the animation.

    // We will now change the tree on the same frame as the animation ends.
197
    await tester.pumpWidget(Container(), const Duration(seconds: 2));
198 199
  });

200
  testWidgets('Read operations on ScrollControllers with no positions fail', (WidgetTester tester) async {
201
    final ScrollController controller = ScrollController();
202 203 204 205 206
    expect(() => controller.offset, throwsAssertionError);
    expect(() => controller.position, throwsAssertionError);
  });

  testWidgets('Read operations on ScrollControllers with more than one position fail', (WidgetTester tester) async {
207
    final ScrollController controller = ScrollController();
208
    await tester.pumpWidget(
209
      Directionality(
210
        textDirection: TextDirection.ltr,
211
        child: ListView(
212
          children: <Widget>[
213
            Container(
214
              constraints: const BoxConstraints(maxHeight: 500.0),
215
              child: ListView(
216 217
                controller: controller,
                children: kStates.map<Widget>((String state) {
218
                  return Container(height: 200.0, child: Text(state));
219 220 221
                }).toList(),
              ),
            ),
222
            Container(
223
              constraints: const BoxConstraints(maxHeight: 500.0),
224
              child: ListView(
225 226
                controller: controller,
                children: kStates.map<Widget>((String state) {
227
                  return Container(height: 200.0, child: Text(state));
228 229 230 231
                }).toList(),
              ),
            ),
          ],
232
        ),
233 234
      ),
    );
235 236 237 238 239 240

    expect(() => controller.offset, throwsAssertionError);
    expect(() => controller.position, throwsAssertionError);
  });

  testWidgets('Write operations on ScrollControllers with no positions fail', (WidgetTester tester) async {
241
    final ScrollController controller = ScrollController();
242 243 244 245
    expect(() => controller.animateTo(1.0, duration: const Duration(seconds: 1), curve: Curves.linear), throwsAssertionError);
    expect(() => controller.jumpTo(1.0), throwsAssertionError);
  });

246
  testWidgets('Write operations on ScrollControllers with more than one position do not throw', (WidgetTester tester) async {
247
    final ScrollController controller = ScrollController();
248
    await tester.pumpWidget(
249
      Directionality(
250
        textDirection: TextDirection.ltr,
251
        child: ListView(
252
          children: <Widget>[
253
            Container(
254
              constraints: const BoxConstraints(maxHeight: 500.0),
255
              child: ListView(
256 257
                controller: controller,
                children: kStates.map<Widget>((String state) {
258
                  return Container(height: 200.0, child: Text(state));
259 260 261
                }).toList(),
              ),
            ),
262
            Container(
263
              constraints: const BoxConstraints(maxHeight: 500.0),
264
              child: ListView(
265 266
                controller: controller,
                children: kStates.map<Widget>((String state) {
267
                  return Container(height: 200.0, child: Text(state));
268 269 270 271
                }).toList(),
              ),
            ),
          ],
272
        ),
273 274
      ),
    );
275

276 277
    controller.jumpTo(1.0);
    controller.animateTo(1.0, duration: const Duration(seconds: 1), curve: Curves.linear);
278
    await tester.pumpAndSettle();
279
  });
280 281

  testWidgets('Scroll controllers notify when the position changes', (WidgetTester tester) async {
282
    final ScrollController controller = ScrollController();
283 284 285 286 287 288 289

    final List<double> log = <double>[];

    controller.addListener(() {
      log.add(controller.offset);
    });

290
    await tester.pumpWidget(
291
      Directionality(
292
        textDirection: TextDirection.ltr,
293
        child: ListView(
294 295
          controller: controller,
          children: kStates.map<Widget>((String state) {
296
            return Container(height: 200.0, child: Text(state));
297 298 299 300
          }).toList(),
        ),
      ),
    );
301 302 303 304 305

    expect(log, isEmpty);

    await tester.drag(find.byType(ListView), const Offset(0.0, -250.0));

306
    expect(log, equals(<double>[ 20.0, 250.0 ]));
307 308 309 310 311 312 313
    log.clear();

    controller.dispose();

    await tester.drag(find.byType(ListView), const Offset(0.0, -130.0));
    expect(log, isEmpty);
  });
314 315

  testWidgets('keepScrollOffset', (WidgetTester tester) async {
316
    final PageStorageBucket bucket = PageStorageBucket();
317 318

    Widget buildFrame(ScrollController controller) {
319
      return Directionality(
320
        textDirection: TextDirection.ltr,
321
        child: PageStorage(
322
          bucket: bucket,
323
          child: KeyedSubtree(
324
            key: const PageStorageKey<String>('ListView'),
325 326
            child: ListView(
              key: UniqueKey(), // it's a different ListView every time
327
              controller: controller,
328 329
              children: List<Widget>.generate(50, (int index) {
                return Container(height: 100.0, child: Text('Item $index'));
330 331
              }).toList(),
            ),
332
          ),
333 334 335 336 337 338 339 340 341
        ),
      );
    }

    // keepScrollOffset: true (the default). The scroll offset is restored
    // when the ListView is recreated with a new ScrollController.

    // The initialScrollOffset is used in this case, because there's no saved
    // scroll offset.
342
    ScrollController controller = ScrollController(initialScrollOffset: 200.0);
343 344 345 346 347 348 349 350 351
    await tester.pumpWidget(buildFrame(controller));
    expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 2')), Offset.zero);

    controller.jumpTo(2000.0);
    await tester.pump();
    expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero);

    // The initialScrollOffset isn't used in this case, because the scrolloffset
    // can be restored.
352
    controller = ScrollController(initialScrollOffset: 25.0);
353 354 355 356 357 358 359 360
    await tester.pumpWidget(buildFrame(controller));
    expect(controller.offset, 2000.0);
    expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 20')), Offset.zero);

    // keepScrollOffset: false. The scroll offset is -not- restored
    // when the ListView is recreated with a new ScrollController and
    // the initialScrollOffset is used.

361
    controller = ScrollController(keepScrollOffset: false, initialScrollOffset: 100.0);
362 363 364 365 366
    await tester.pumpWidget(buildFrame(controller));
    expect(controller.offset, 100.0);
    expect(tester.getTopLeft(find.widgetWithText(Container, 'Item 1')), Offset.zero);

  });
367
}