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

360
Future<void> eventFiring(WidgetTester tester) async {
361
  await tester.pump(Duration.zero);
362 363 364
}

class StringCollector extends StreamBuilderBase<String, List<String>> {
365
  const StringCollector({ Key? key, Stream<String>? stream }) : super(key: key, stream: stream);
366 367 368 369 370 371 372 373 374 375 376

  @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
377
  List<String> afterError(List<String> current, dynamic error, StackTrace stackTrace) => current..add('error:$error stackTrace:$stackTrace');
378 379 380 381 382 383 384 385

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

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

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