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

import 'dart:async';

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

10
void main() {
11
  Widget snapshotText(BuildContext context, AsyncSnapshot<String> snapshot) {
12
    return Text(snapshot.toString(), textDirection: TextDirection.ltr);
13
  }
14
  group('AsyncSnapshot', () {
15 16 17 18 19
    test('requiring data succeeds if data is present', () {
      expect(
        const AsyncSnapshot<String>.withData(ConnectionState.done, 'hello').requireData,
        'hello',
      );
20
    });
21 22 23 24 25
    test('requiring data fails if there is an error', () {
      expect(
        () => const AsyncSnapshot<String>.withError(ConnectionState.done, 'error').requireData,
        throwsA(equals('error')),
      );
26
    });
27 28 29 30 31
    test('requiring data fails if snapshot has neither data nor error', () {
      expect(
        () => const AsyncSnapshot<String>.nothing().requireData,
        throwsStateError,
      );
32
    });
33 34 35 36 37 38 39 40
    test('AsyncSnapshot basic constructors', () {
      expect(const AsyncSnapshot<int>.nothing().connectionState, ConnectionState.none);
      expect(const AsyncSnapshot<int>.nothing().data, isNull);
      expect(const AsyncSnapshot<int>.nothing().error, isNull);
      expect(const AsyncSnapshot<int>.waiting().connectionState, ConnectionState.waiting);
      expect(const AsyncSnapshot<int>.waiting().data, isNull);
      expect(const AsyncSnapshot<int>.waiting().error, isNull);
    });
41
  });
42 43
  group('Async smoke tests', () {
    testWidgets('FutureBuilder', (WidgetTester tester) async {
44 45
      await tester.pumpWidget(FutureBuilder<String>(
        future: Future<String>.value('hello'),
46
        builder: snapshotText,
47 48 49 50
      ));
      await eventFiring(tester);
    });
    testWidgets('StreamBuilder', (WidgetTester tester) async {
51
      await tester.pumpWidget(StreamBuilder<String>(
52
        stream: Stream<String>.fromIterable(<String>['hello', 'world']),
53
        builder: snapshotText,
54 55 56 57
      ));
      await eventFiring(tester);
    });
    testWidgets('StreamFold', (WidgetTester tester) async {
58 59
      await tester.pumpWidget(StringCollector(
        stream: Stream<String>.fromIterable(<String>['hello', 'world'])
60 61 62 63 64 65
      ));
      await eventFiring(tester);
    });
  });
  group('FutureBuilder', () {
    testWidgets('gracefully handles transition from null future', (WidgetTester tester) async {
66 67
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(FutureBuilder<String>(
68
        key: key, future: null, builder: snapshotText,
69
      ));
70
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
71 72
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
73
        key: key, future: completer.future, builder: snapshotText,
74
      ));
75
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
76 77
    });
    testWidgets('gracefully handles transition to null future', (WidgetTester tester) async {
78 79 80
      final GlobalKey key = GlobalKey();
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
81
        key: key, future: completer.future, builder: snapshotText,
82
      ));
83
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
84
      await tester.pumpWidget(FutureBuilder<String>(
85
        key: key, future: null, builder: snapshotText,
86
      ));
87
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
88 89
      completer.complete('hello');
      await eventFiring(tester);
90
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
91 92
    });
    testWidgets('gracefully handles transition to other future', (WidgetTester tester) async {
93 94 95 96
      final GlobalKey key = GlobalKey();
      final Completer<String> completerA = Completer<String>();
      final Completer<String> completerB = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
97
        key: key, future: completerA.future, builder: snapshotText,
98
      ));
99
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
100
      await tester.pumpWidget(FutureBuilder<String>(
101
        key: key, future: completerB.future, builder: snapshotText,
102
      ));
103
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
104 105 106
      completerB.complete('B');
      completerA.complete('A');
      await eventFiring(tester);
107
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, B, null)'), findsOneWidget);
108 109
    });
    testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async {
110 111
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
112
        future: completer.future, builder: snapshotText,
113
      ));
114
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
115 116
      completer.complete('hello');
      await eventFiring(tester);
117
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsOneWidget);
118 119
    });
    testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async {
120 121
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
122
        future: completer.future, builder: snapshotText,
123
      ));
124
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
125 126
      completer.completeError('bad');
      await eventFiring(tester);
127
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad)'), findsOneWidget);
128
    });
129 130
    testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
      final GlobalKey key = GlobalKey();
131
      await tester.pumpWidget(FutureBuilder<String>(
132 133 134 135
        key: key,
        future: null,
        builder: snapshotText,
        initialData: 'I',
136
      ));
137
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
138
    });
139 140 141 142 143 144 145
    testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(FutureBuilder<String>(
        key: key,
        future: null,
        builder: snapshotText,
        initialData: 'I',
146
      ));
147 148 149 150 151 152 153 154 155
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
        key: key,
        future: completer.future,
        builder: snapshotText,
        initialData: 'Ignored',
      ));
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
156
    });
157 158 159
  });
  group('StreamBuilder', () {
    testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
160
      final GlobalKey key = GlobalKey();
161
      await tester.pumpWidget(StreamBuilder<String>(
162
        key: key, stream: null, builder: snapshotText,
163
      ));
164
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
165
      final StreamController<String> controller = StreamController<String>();
166
      await tester.pumpWidget(StreamBuilder<String>(
167
        key: key, stream: controller.stream, builder: snapshotText,
168
      ));
169
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
170 171
    });
    testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
172 173
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
174
      await tester.pumpWidget(StreamBuilder<String>(
175
        key: key, stream: controller.stream, builder: snapshotText,
176
      ));
177 178
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
      await tester.pumpWidget(StreamBuilder<String>(
179
        key: key, stream: null, builder: snapshotText,
180
      ));
181
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsOneWidget);
182 183
    });
    testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
184 185 186
      final GlobalKey key = GlobalKey();
      final StreamController<String> controllerA = StreamController<String>();
      final StreamController<String> controllerB = StreamController<String>();
187
      await tester.pumpWidget(StreamBuilder<String>(
188
        key: key, stream: controllerA.stream, builder: snapshotText,
189
      ));
190 191
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
      await tester.pumpWidget(StreamBuilder<String>(
192
        key: key, stream: controllerB.stream, builder: snapshotText,
193 194 195 196
      ));
      controllerB.add('B');
      controllerA.add('A');
      await eventFiring(tester);
197
      expect(find.text('AsyncSnapshot<String>(ConnectionState.active, B, null)'), findsOneWidget);
198 199
    });
    testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async {
200 201
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
202
      await tester.pumpWidget(StreamBuilder<String>(
203
        key: key, stream: controller.stream, builder: snapshotText,
204
      ));
205
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsOneWidget);
206 207 208
      controller.add('1');
      controller.add('2');
      await eventFiring(tester);
209
      expect(find.text('AsyncSnapshot<String>(ConnectionState.active, 2, null)'), findsOneWidget);
210 211 212
      controller.add('3');
      controller.addError('bad');
      await eventFiring(tester);
213
      expect(find.text('AsyncSnapshot<String>(ConnectionState.active, null, bad)'), findsOneWidget);
214 215 216
      controller.add('4');
      controller.close();
      await eventFiring(tester);
217
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, 4, null)'), findsOneWidget);
218
    });
219
    testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
220 221
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StreamBuilder<String>(
222 223 224 225
        stream: controller.stream,
        builder: snapshotText,
        initialData: 'I',
      ));
226
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
227 228
    });
    testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
229 230
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(StreamBuilder<String>(
231 232 233 234 235
        key: key,
        stream: null,
        builder: snapshotText,
        initialData: 'I',
      ));
236
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsOneWidget);
237 238
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StreamBuilder<String>(
239 240 241 242 243
        key: key,
        stream: controller.stream,
        builder: snapshotText,
        initialData: 'Ignored',
      ));
244
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsOneWidget);
245
    });
246 247 248
  });
  group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () {
    testWidgets('when completing with data', (WidgetTester tester) async {
249 250 251
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: completer.future, builder: snapshotText),
252
        StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
253
      ]));
254
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsNWidgets(2));
255 256
      completer.complete('hello');
      await eventFiring(tester);
257
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsNWidgets(2));
258 259
    });
    testWidgets('when completing with error', (WidgetTester tester) async {
260 261 262
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: completer.future, builder: snapshotText),
263
        StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
264
      ]));
265
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null)'), findsNWidgets(2));
266 267
      completer.completeError('bad');
      await eventFiring(tester);
268
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad)'), findsNWidgets(2));
269 270
    });
    testWidgets('when Future is null', (WidgetTester tester) async {
271 272
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: null, builder: snapshotText),
273
        StreamBuilder<String>(stream: null, builder: snapshotText),
274
      ]));
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null)'), findsNWidgets(2));
    });
    testWidgets('when initialData is used with null Future and Stream', (WidgetTester tester) async {
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: null, builder: snapshotText, initialData: 'I'),
        StreamBuilder<String>(stream: null, builder: snapshotText, initialData: 'I'),
      ]));
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null)'), findsNWidgets(2));
    });
    testWidgets('when using initialData and completing with data', (WidgetTester tester) async {
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: completer.future, builder: snapshotText, initialData: 'I'),
        StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText, initialData: 'I'),
      ]));
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null)'), findsNWidgets(2));
      completer.complete('hello');
      await eventFiring(tester);
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null)'), findsNWidgets(2));
294
    });
295 296 297
  });
  group('StreamBuilderBase', () {
    testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
298 299
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(StringCollector(key: key, stream: null));
300
      expect(find.text(''), findsOneWidget);
301 302
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controller.stream));
303 304 305
      expect(find.text('conn'), findsOneWidget);
    });
    testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
306 307 308
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controller.stream));
309
      expect(find.text('conn'), findsOneWidget);
310
      await tester.pumpWidget(StringCollector(key: key, stream: null));
311 312 313
      expect(find.text('conn, disc'), findsOneWidget);
    });
    testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
314 315 316 317 318
      final GlobalKey key = GlobalKey();
      final StreamController<String> controllerA = StreamController<String>();
      final StreamController<String> controllerB = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controllerA.stream));
      await tester.pumpWidget(StringCollector(key: key, stream: controllerB.stream));
319 320 321 322 323 324
      controllerA.add('A');
      controllerB.add('B');
      await eventFiring(tester);
      expect(find.text('conn, disc, conn, data:B'), findsOneWidget);
    });
    testWidgets('tracks events and errors until completion', (WidgetTester tester) async {
325 326 327
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controller.stream));
328 329 330 331 332 333 334 335 336 337
      controller.add('1');
      controller.addError('bad');
      controller.add('2');
      controller.close();
      await eventFiring(tester);
      expect(find.text('conn, data:1, error:bad, data:2, done'), findsOneWidget);
    });
  });
}

338
Future<void> eventFiring(WidgetTester tester) async {
339
  await tester.pump(Duration.zero);
340 341 342
}

class StringCollector extends StreamBuilderBase<String, List<String>> {
343
  const StringCollector({ Key? key, Stream<String>? stream }) : super(key: key, stream: stream);
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363

  @override
  List<String> initial() => <String>[];

  @override
  List<String> afterConnected(List<String> current) => current..add('conn');

  @override
  List<String> afterData(List<String> current, String data) => current..add('data:$data');

  @override
  List<String> afterError(List<String> current, dynamic error) => current..add('error:$error');

  @override
  List<String> afterDone(List<String> current) => current..add('done');

  @override
  List<String> afterDisconnected(List<String> current) => current..add('disc');

  @override
364
  Widget build(BuildContext context, List<String> currentSummary) => Text(currentSummary.join(', '), textDirection: TextDirection.ltr);
365
}