async_test.dart 19.7 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 20 21 22 23 24 25 26 27 28
    test('requiring data preserves the stackTrace', () {
      final StackTrace originalStackTrace = StackTrace.current;

      try {
        AsyncSnapshot<String>.withError(
          ConnectionState.done,
          Error(),
          originalStackTrace,
        ).requireData;
        fail('requireData did not throw');
      } catch (error, stackTrace) {
        expect(stackTrace, originalStackTrace);
      }
    });
29 30 31 32 33
    test('requiring data succeeds if data is present', () {
      expect(
        const AsyncSnapshot<String>.withData(ConnectionState.done, 'hello').requireData,
        'hello',
      );
34
    });
35 36 37 38 39
    test('requiring data fails if there is an error', () {
      expect(
        () => const AsyncSnapshot<String>.withError(ConnectionState.done, 'error').requireData,
        throwsA(equals('error')),
      );
40
    });
41 42 43 44 45
    test('requiring data fails if snapshot has neither data nor error', () {
      expect(
        () => const AsyncSnapshot<String>.nothing().requireData,
        throwsStateError,
      );
46
    });
47 48 49 50
    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);
51
      expect(const AsyncSnapshot<int>.nothing().stackTrace, isNull);
52 53 54
      expect(const AsyncSnapshot<int>.waiting().connectionState, ConnectionState.waiting);
      expect(const AsyncSnapshot<int>.waiting().data, isNull);
      expect(const AsyncSnapshot<int>.waiting().error, isNull);
55 56 57 58 59 60 61
      expect(const AsyncSnapshot<int>.waiting().stackTrace, isNull);
    });
    test('withError uses empty stack trace if no stack trace is specified', () {
      // We need to store the error as a local variable in order for the
      // equality check on the error to be true.
      final Error error = Error();
      expect(
62
        AsyncSnapshot<int>.withError(ConnectionState.done, error),
63
        AsyncSnapshot<int>.withError(ConnectionState.done, error),
64
      );
65
    });
66
  });
67 68
  group('Async smoke tests', () {
    testWidgets('FutureBuilder', (WidgetTester tester) async {
69 70
      await tester.pumpWidget(FutureBuilder<String>(
        future: Future<String>.value('hello'),
71
        builder: snapshotText,
72 73 74 75
      ));
      await eventFiring(tester);
    });
    testWidgets('StreamBuilder', (WidgetTester tester) async {
76
      await tester.pumpWidget(StreamBuilder<String>(
77
        stream: Stream<String>.fromIterable(<String>['hello', 'world']),
78
        builder: snapshotText,
79 80 81 82
      ));
      await eventFiring(tester);
    });
    testWidgets('StreamFold', (WidgetTester tester) async {
83
      await tester.pumpWidget(StringCollector(
84
        stream: Stream<String>.fromIterable(<String>['hello', 'world']),
85 86 87 88 89 90
      ));
      await eventFiring(tester);
    });
  });
  group('FutureBuilder', () {
    testWidgets('gracefully handles transition from null future', (WidgetTester tester) async {
91 92
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(FutureBuilder<String>(
93
        key: key, builder: snapshotText,
94
      ));
95
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget);
96 97
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
98
        key: key, future: completer.future, builder: snapshotText,
99
      ));
100
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
101 102
    });
    testWidgets('gracefully handles transition to null future', (WidgetTester tester) async {
103 104 105
      final GlobalKey key = GlobalKey();
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
106
        key: key, future: completer.future, builder: snapshotText,
107
      ));
108
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
109
      await tester.pumpWidget(FutureBuilder<String>(
110
        key: key, builder: snapshotText,
111
      ));
112
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget);
113 114
      completer.complete('hello');
      await eventFiring(tester);
115
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget);
116 117
    });
    testWidgets('gracefully handles transition to other future', (WidgetTester tester) async {
118 119 120 121
      final GlobalKey key = GlobalKey();
      final Completer<String> completerA = Completer<String>();
      final Completer<String> completerB = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
122
        key: key, future: completerA.future, builder: snapshotText,
123
      ));
124
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
125
      await tester.pumpWidget(FutureBuilder<String>(
126
        key: key, future: completerB.future, builder: snapshotText,
127
      ));
128
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
129 130 131
      completerB.complete('B');
      completerA.complete('A');
      await eventFiring(tester);
132
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, B, null, null)'), findsOneWidget);
133 134
    });
    testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async {
135 136
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
137
        future: completer.future, builder: snapshotText,
138
      ));
139
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
140 141
      completer.complete('hello');
      await eventFiring(tester);
142
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null, null)'), findsOneWidget);
143 144
    });
    testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async {
145 146
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
147
        future: completer.future, builder: snapshotText,
148
      ));
149 150
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
      completer.completeError('bad', StackTrace.fromString('trace'));
151
      await eventFiring(tester);
152
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad, trace)'), findsOneWidget);
153
    });
154 155
    testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
      final GlobalKey key = GlobalKey();
156
      await tester.pumpWidget(FutureBuilder<String>(
157 158 159
        key: key,
        builder: snapshotText,
        initialData: 'I',
160
      ));
161
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, null)'), findsOneWidget);
162
    });
163 164 165 166 167 168
    testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(FutureBuilder<String>(
        key: key,
        builder: snapshotText,
        initialData: 'I',
169
      ));
170
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, null)'), findsOneWidget);
171 172 173 174 175 176 177
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(FutureBuilder<String>(
        key: key,
        future: completer.future,
        builder: snapshotText,
        initialData: 'Ignored',
      ));
178
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null, null)'), findsOneWidget);
179
    });
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    testWidgets('debugRethrowError rethrows caught error', (WidgetTester tester) async {
      FutureBuilder.debugRethrowError = true;
      final Completer<void> caughtError = Completer<void>();
      await runZonedGuarded(() async {
        final Completer<String> completer = Completer<String>();
        await tester.pumpWidget(FutureBuilder<String>(
          future: completer.future,
          builder: snapshotText,
        ), const Duration(seconds: 1));
        completer.completeError('bad');
      }, (Object error, StackTrace stack) {
        expectSync(error, equals('bad'));
        caughtError.complete();
      });
      await tester.pumpAndSettle();
      expectSync(caughtError.isCompleted, isTrue);
      FutureBuilder.debugRethrowError = false;
    });
198 199 200
  });
  group('StreamBuilder', () {
    testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
201
      final GlobalKey key = GlobalKey();
202
      await tester.pumpWidget(StreamBuilder<String>(
203
        key: key, builder: snapshotText,
204
      ));
205
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget);
206
      final StreamController<String> controller = StreamController<String>();
207
      await tester.pumpWidget(StreamBuilder<String>(
208
        key: key, stream: controller.stream, builder: snapshotText,
209
      ));
210
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
211 212
    });
    testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
213 214
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
215
      await tester.pumpWidget(StreamBuilder<String>(
216
        key: key, stream: controller.stream, builder: snapshotText,
217
      ));
218
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
219
      await tester.pumpWidget(StreamBuilder<String>(
220
        key: key, builder: snapshotText,
221
      ));
222
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget);
223 224
    });
    testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
225 226 227
      final GlobalKey key = GlobalKey();
      final StreamController<String> controllerA = StreamController<String>();
      final StreamController<String> controllerB = StreamController<String>();
228
      await tester.pumpWidget(StreamBuilder<String>(
229
        key: key, stream: controllerA.stream, builder: snapshotText,
230
      ));
231
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
232
      await tester.pumpWidget(StreamBuilder<String>(
233
        key: key, stream: controllerB.stream, builder: snapshotText,
234 235 236 237
      ));
      controllerB.add('B');
      controllerA.add('A');
      await eventFiring(tester);
238
      expect(find.text('AsyncSnapshot<String>(ConnectionState.active, B, null, null)'), findsOneWidget);
239 240
    });
    testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async {
241 242
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
243
      await tester.pumpWidget(StreamBuilder<String>(
244
        key: key, stream: controller.stream, builder: snapshotText,
245
      ));
246
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget);
247 248 249
      controller.add('1');
      controller.add('2');
      await eventFiring(tester);
250
      expect(find.text('AsyncSnapshot<String>(ConnectionState.active, 2, null, null)'), findsOneWidget);
251
      controller.add('3');
252
      controller.addError('bad', StackTrace.fromString('trace'));
253
      await eventFiring(tester);
254
      expect(find.text('AsyncSnapshot<String>(ConnectionState.active, null, bad, trace)'), findsOneWidget);
255 256 257
      controller.add('4');
      controller.close();
      await eventFiring(tester);
258
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, 4, null, null)'), findsOneWidget);
259
    });
260
    testWidgets('runs the builder using given initial data', (WidgetTester tester) async {
261 262
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StreamBuilder<String>(
263 264 265 266
        stream: controller.stream,
        builder: snapshotText,
        initialData: 'I',
      ));
267
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null, null)'), findsOneWidget);
268 269
    });
    testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async {
270 271
      final GlobalKey key = GlobalKey();
      await tester.pumpWidget(StreamBuilder<String>(
272 273 274 275
        key: key,
        builder: snapshotText,
        initialData: 'I',
      ));
276
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, null)'), findsOneWidget);
277 278
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StreamBuilder<String>(
279 280 281 282 283
        key: key,
        stream: controller.stream,
        builder: snapshotText,
        initialData: 'Ignored',
      ));
284
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null, null)'), findsOneWidget);
285
    });
286 287 288
  });
  group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () {
    testWidgets('when completing with data', (WidgetTester tester) async {
289 290 291
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: completer.future, builder: snapshotText),
292
        StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
293
      ]));
294
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsNWidgets(2));
295 296
      completer.complete('hello');
      await eventFiring(tester);
297 298 299 300 301 302 303 304 305 306 307 308
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null, null)'), findsNWidgets(2));
    });
    testWidgets('when completing with error and with empty stack trace', (WidgetTester tester) async {
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: completer.future, builder: snapshotText),
        StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
      ]));
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsNWidgets(2));
      completer.completeError('bad', StackTrace.empty);
      await eventFiring(tester);
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad, )'), findsNWidgets(2));
309
    });
310
    testWidgets('when completing with error and with stack trace', (WidgetTester tester) async {
311 312 313
      final Completer<String> completer = Completer<String>();
      await tester.pumpWidget(Column(children: <Widget>[
        FutureBuilder<String>(future: completer.future, builder: snapshotText),
314
        StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText),
315
      ]));
316 317
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsNWidgets(2));
      completer.completeError('bad', StackTrace.fromString('trace'));
318
      await eventFiring(tester);
319
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad, trace)'), findsNWidgets(2));
320 321
    });
    testWidgets('when Future is null', (WidgetTester tester) async {
322
      await tester.pumpWidget(Column(children: <Widget>[
323 324
        FutureBuilder<String>(builder: snapshotText),
        StreamBuilder<String>(builder: snapshotText),
325
      ]));
326
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsNWidgets(2));
327 328 329
    });
    testWidgets('when initialData is used with null Future and Stream', (WidgetTester tester) async {
      await tester.pumpWidget(Column(children: <Widget>[
330 331
        FutureBuilder<String>(builder: snapshotText, initialData: 'I'),
        StreamBuilder<String>(builder: snapshotText, initialData: 'I'),
332
      ]));
333
      expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, null)'), findsNWidgets(2));
334 335 336 337 338 339 340
    });
    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'),
      ]));
341
      expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null, null)'), findsNWidgets(2));
342 343
      completer.complete('hello');
      await eventFiring(tester);
344
      expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null, null)'), findsNWidgets(2));
345
    });
346 347 348
  });
  group('StreamBuilderBase', () {
    testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async {
349
      final GlobalKey key = GlobalKey();
350
      await tester.pumpWidget(StringCollector(key: key));
351
      expect(find.text(''), findsOneWidget);
352 353
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controller.stream));
354 355 356
      expect(find.text('conn'), findsOneWidget);
    });
    testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async {
357 358 359
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controller.stream));
360
      expect(find.text('conn'), findsOneWidget);
361
      await tester.pumpWidget(StringCollector(key: key));
362 363 364
      expect(find.text('conn, disc'), findsOneWidget);
    });
    testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async {
365 366 367 368 369
      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));
370 371 372 373 374 375
      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 {
376 377 378
      final GlobalKey key = GlobalKey();
      final StreamController<String> controller = StreamController<String>();
      await tester.pumpWidget(StringCollector(key: key, stream: controller.stream));
379
      controller.add('1');
380
      controller.addError('bad', StackTrace.fromString('trace'));
381 382 383
      controller.add('2');
      controller.close();
      await eventFiring(tester);
384
      expect(find.text('conn, data:1, error:bad stackTrace:trace, data:2, done'), findsOneWidget);
385 386 387 388
    });
  });
}

389
Future<void> eventFiring(WidgetTester tester) async {
390
  await tester.pump(Duration.zero);
391 392 393
}

class StringCollector extends StreamBuilderBase<String, List<String>> {
394
  const StringCollector({ super.key, super.stream });
395 396 397 398 399 400 401 402 403 404 405

  @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
406
  List<String> afterError(List<String> current, dynamic error, StackTrace stackTrace) => current..add('error:$error stackTrace:$stackTrace');
407 408 409 410 411 412 413 414

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

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

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