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

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

import 'states.dart';

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

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

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

    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);
47
    await tester.pumpAndSettle();
48 49 50 51

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

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

    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));

76
    final ScrollController controller2 = ScrollController();
77

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

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

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

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

    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));
  });
131 132

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

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

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

    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));

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

    expect(controller.offset, equals(105.0));
    expect(realOffset(), equals(controller.offset));
  });
176 177

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

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

    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.
195
    await tester.pumpWidget(Container(), const Duration(seconds: 2));
196 197
  });

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

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

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

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

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

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

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

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

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

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

    expect(log, isEmpty);

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

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

    controller.dispose();

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

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

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

    // 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.
340
    ScrollController controller = ScrollController(initialScrollOffset: 200.0);
341
    await tester.pumpWidget(buildFrame(controller));
342
    expect(tester.getTopLeft(find.widgetWithText(SizedBox, 'Item 2')), Offset.zero);
343 344 345

    controller.jumpTo(2000.0);
    await tester.pump();
346
    expect(tester.getTopLeft(find.widgetWithText(SizedBox, 'Item 20')), Offset.zero);
347 348 349

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

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

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

  });
365
}