| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| Widget snapshotText(BuildContext context, AsyncSnapshot<String> snapshot) { |
| return Text(snapshot.toString(), textDirection: TextDirection.ltr); |
| } |
| group('AsyncSnapshot', () { |
| test('requiring data succeeds if data is present', () { |
| expect( |
| const AsyncSnapshot<String>.withData(ConnectionState.done, 'hello').requireData, |
| 'hello', |
| ); |
| }); |
| test('requiring data fails if there is an error', () { |
| expect( |
| () => const AsyncSnapshot<String>.withError(ConnectionState.done, 'error').requireData, |
| throwsA(equals('error')), |
| ); |
| }); |
| test('requiring data fails if snapshot has neither data nor error', () { |
| expect( |
| () => const AsyncSnapshot<String>.nothing().requireData, |
| throwsStateError, |
| ); |
| }); |
| 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>.nothing().stackTrace, isNull); |
| expect(const AsyncSnapshot<int>.waiting().connectionState, ConnectionState.waiting); |
| expect(const AsyncSnapshot<int>.waiting().data, isNull); |
| expect(const AsyncSnapshot<int>.waiting().error, isNull); |
| 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( |
| AsyncSnapshot<int>.withError(ConnectionState.done, error), |
| AsyncSnapshot<int>.withError(ConnectionState.done, error), |
| ); |
| }); |
| }); |
| group('Async smoke tests', () { |
| testWidgets('FutureBuilder', (WidgetTester tester) async { |
| await tester.pumpWidget(FutureBuilder<String>( |
| future: Future<String>.value('hello'), |
| builder: snapshotText, |
| )); |
| await eventFiring(tester); |
| }); |
| testWidgets('StreamBuilder', (WidgetTester tester) async { |
| await tester.pumpWidget(StreamBuilder<String>( |
| stream: Stream<String>.fromIterable(<String>['hello', 'world']), |
| builder: snapshotText, |
| )); |
| await eventFiring(tester); |
| }); |
| testWidgets('StreamFold', (WidgetTester tester) async { |
| await tester.pumpWidget(StringCollector( |
| stream: Stream<String>.fromIterable(<String>['hello', 'world']), |
| )); |
| await eventFiring(tester); |
| }); |
| }); |
| group('FutureBuilder', () { |
| testWidgets('gracefully handles transition from null future', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget); |
| final Completer<String> completer = Completer<String>(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, future: completer.future, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| }); |
| testWidgets('gracefully handles transition to null future', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final Completer<String> completer = Completer<String>(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, future: completer.future, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget); |
| completer.complete('hello'); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget); |
| }); |
| testWidgets('gracefully handles transition to other future', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final Completer<String> completerA = Completer<String>(); |
| final Completer<String> completerB = Completer<String>(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, future: completerA.future, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, future: completerB.future, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| completerB.complete('B'); |
| completerA.complete('A'); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.done, B, null, null)'), findsOneWidget); |
| }); |
| testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async { |
| final Completer<String> completer = Completer<String>(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| future: completer.future, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| completer.complete('hello'); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null, null)'), findsOneWidget); |
| }); |
| testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async { |
| final Completer<String> completer = Completer<String>(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| future: completer.future, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| completer.completeError('bad', StackTrace.fromString('trace')); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad, trace)'), findsOneWidget); |
| }); |
| testWidgets('runs the builder using given initial data', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, |
| builder: snapshotText, |
| initialData: 'I', |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, null)'), findsOneWidget); |
| }); |
| testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(FutureBuilder<String>( |
| key: key, |
| builder: snapshotText, |
| initialData: 'I', |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, 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, null)'), findsOneWidget); |
| }); |
| 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; |
| }); |
| }); |
| group('StreamBuilder', () { |
| testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, stream: controller.stream, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| }); |
| testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, stream: controller.stream, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, null, null)'), findsOneWidget); |
| }); |
| testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final StreamController<String> controllerA = StreamController<String>(); |
| final StreamController<String> controllerB = StreamController<String>(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, stream: controllerA.stream, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, stream: controllerB.stream, builder: snapshotText, |
| )); |
| controllerB.add('B'); |
| controllerA.add('A'); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.active, B, null, null)'), findsOneWidget); |
| }); |
| testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, stream: controller.stream, builder: snapshotText, |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsOneWidget); |
| controller.add('1'); |
| controller.add('2'); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.active, 2, null, null)'), findsOneWidget); |
| controller.add('3'); |
| controller.addError('bad', StackTrace.fromString('trace')); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.active, null, bad, trace)'), findsOneWidget); |
| controller.add('4'); |
| controller.close(); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.done, 4, null, null)'), findsOneWidget); |
| }); |
| testWidgets('runs the builder using given initial data', (WidgetTester tester) async { |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| stream: controller.stream, |
| builder: snapshotText, |
| initialData: 'I', |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null, null)'), findsOneWidget); |
| }); |
| testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, |
| builder: snapshotText, |
| initialData: 'I', |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, null)'), findsOneWidget); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StreamBuilder<String>( |
| key: key, |
| stream: controller.stream, |
| builder: snapshotText, |
| initialData: 'Ignored', |
| )); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, I, null, null)'), findsOneWidget); |
| }); |
| }); |
| group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () { |
| testWidgets('when 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), |
| StreamBuilder<String>(stream: completer.future.asStream(), builder: snapshotText), |
| ])); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)'), findsNWidgets(2)); |
| completer.complete('hello'); |
| await eventFiring(tester); |
| 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)); |
| }); |
| testWidgets('when completing with error and with 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.fromString('trace')); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.done, null, bad, trace)'), findsNWidgets(2)); |
| }); |
| testWidgets('when Future is null', (WidgetTester tester) async { |
| await tester.pumpWidget(Column(children: <Widget>[ |
| FutureBuilder<String>(builder: snapshotText), |
| StreamBuilder<String>(builder: snapshotText), |
| ])); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, null, 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>(builder: snapshotText, initialData: 'I'), |
| StreamBuilder<String>(builder: snapshotText, initialData: 'I'), |
| ])); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.none, I, null, 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, null)'), findsNWidgets(2)); |
| completer.complete('hello'); |
| await eventFiring(tester); |
| expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null, null)'), findsNWidgets(2)); |
| }); |
| }); |
| group('StreamBuilderBase', () { |
| testWidgets('gracefully handles transition from null stream', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(StringCollector(key: key)); |
| expect(find.text(''), findsOneWidget); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StringCollector(key: key, stream: controller.stream)); |
| expect(find.text('conn'), findsOneWidget); |
| }); |
| testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StringCollector(key: key, stream: controller.stream)); |
| expect(find.text('conn'), findsOneWidget); |
| await tester.pumpWidget(StringCollector(key: key)); |
| expect(find.text('conn, disc'), findsOneWidget); |
| }); |
| testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async { |
| 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)); |
| 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 { |
| final GlobalKey key = GlobalKey(); |
| final StreamController<String> controller = StreamController<String>(); |
| await tester.pumpWidget(StringCollector(key: key, stream: controller.stream)); |
| controller.add('1'); |
| controller.addError('bad', StackTrace.fromString('trace')); |
| controller.add('2'); |
| controller.close(); |
| await eventFiring(tester); |
| expect(find.text('conn, data:1, error:bad stackTrace:trace, data:2, done'), findsOneWidget); |
| }); |
| }); |
| } |
| |
| Future<void> eventFiring(WidgetTester tester) async { |
| await tester.pump(Duration.zero); |
| } |
| |
| class StringCollector extends StreamBuilderBase<String, List<String>> { |
| const StringCollector({ Key? key, Stream<String>? stream }) : super(key: key, stream: stream); |
| |
| @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, StackTrace stackTrace) => current..add('error:$error stackTrace:$stackTrace'); |
| |
| @override |
| List<String> afterDone(List<String> current) => current..add('done'); |
| |
| @override |
| List<String> afterDisconnected(List<String> current) => current..add('disc'); |
| |
| @override |
| Widget build(BuildContext context, List<String> currentSummary) => Text(currentSummary.join(', '), textDirection: TextDirection.ltr); |
| } |