async_test.dart 16.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
import 'dart:async';

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

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

332
Future<void> eventFiring(WidgetTester tester) async {
333
  await tester.pump(Duration.zero);
334 335 336
}

class StringCollector extends StreamBuilderBase<String, List<String>> {
337
  const StringCollector({ Key key, Stream<String> stream }) : super(key: key, stream: stream);
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357

  @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
358
  Widget build(BuildContext context, List<String> currentSummary) => Text(currentSummary.join(', '), textDirection: TextDirection.ltr);
359
}