From 85f0aea87ee949b74bdf5e60340ed2e7ecff53ee Mon Sep 17 00:00:00 2001 From: creativecreatorormaybenot Date: Sat, 14 Nov 2020 01:13:02 +0000 Subject: [PATCH] Add stackTrace to AsyncSnapshot (#69507) --- packages/flutter/lib/src/widgets/async.dart | 96 ++++++++++------ packages/flutter/test/widgets/async_test.dart | 108 +++++++++++------- 2 files changed, 125 insertions(+), 79 deletions(-) diff --git a/packages/flutter/lib/src/widgets/async.dart b/packages/flutter/lib/src/widgets/async.dart index b78acbd1b4..b199d4173a 100644 --- a/packages/flutter/lib/src/widgets/async.dart +++ b/packages/flutter/lib/src/widgets/async.dart @@ -75,10 +75,11 @@ abstract class StreamBuilderBase extends StatefulWidget { /// is combined with the new data item in the fold computation. S afterData(S current, T data); - /// Returns an updated version of the [current] summary following an error. + /// Returns an updated version of the [current] summary following an error + /// with a stack trace. /// /// The default implementation returns [current] as is. - S afterError(S current, Object error) => current; + S afterError(S current, Object error, StackTrace stackTrace) => current; /// Returns an updated version of the [current] summary following stream /// termination. @@ -138,9 +139,9 @@ class _StreamBuilderBaseState extends State> { setState(() { _summary = widget.afterData(_summary, data); }); - }, onError: (Object error) { + }, onError: (Object error, StackTrace stackTrace) { setState(() { - _summary = widget.afterError(_summary, error); + _summary = widget.afterError(_summary, error, stackTrace); }); }, onDone: () { setState(() { @@ -204,22 +205,31 @@ enum ConnectionState { @immutable class AsyncSnapshot { /// Creates an [AsyncSnapshot] with the specified [connectionState], - /// and optionally either [data] or [error] (but not both). - const AsyncSnapshot._(this.connectionState, this.data, this.error) + /// and optionally either [data] or [error] with an optional [stackTrace] + /// (but not both data and error). + const AsyncSnapshot._(this.connectionState, this.data, this.error, this.stackTrace) : assert(connectionState != null), - assert(!(data != null && error != null)); + assert(!(data != null && error != null)), + assert(stackTrace == null || error != null); /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error. - const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null); + const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null, null); /// Creates an [AsyncSnapshot] in [ConnectionState.waiting] with null data and error. - const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null); + const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null, null); /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data]. - const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null); + const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null, null); - /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [error]. - const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, null, error); + /// Creates an [AsyncSnapshot] in the specified [state] with the specified [error] + /// and a [stackTrace]. + /// + /// If no [stackTrace] is explicitly specified, [StackTrace.empty] will be used instead. + const AsyncSnapshot.withError( + ConnectionState state, + Object error, [ + StackTrace stackTrace = StackTrace.empty, + ]) : this._(state, null, error, stackTrace); /// Current state of connection to the asynchronous computation. final ConnectionState connectionState; @@ -254,11 +264,20 @@ class AsyncSnapshot { /// If [data] is not null, this will be null. final Object? error; + /// The latest stack trace object received by the asynchronous computation. + /// + /// This will not be null iff [error] is not null. Consequently, [stackTrace] + /// will be non-null when [hasError] is true. + /// + /// However, even when not null, [stackTrace] might be empty. The stack trace + /// is empty when there is an error but no stack trace has been provided. + final StackTrace? stackTrace; + /// Returns a snapshot like this one, but in the specified [state]. /// - /// The [data] and [error] fields persist unmodified, even if the new state is - /// [ConnectionState.none]. - AsyncSnapshot inState(ConnectionState state) => AsyncSnapshot._(state, data, error); + /// The [data], [error], and [stackTrace] fields persist unmodified, even if + /// the new state is [ConnectionState.none]. + AsyncSnapshot inState(ConnectionState state) => AsyncSnapshot._(state, data, error, stackTrace); /// Returns whether this snapshot contains a non-null [data] value. /// @@ -275,7 +294,7 @@ class AsyncSnapshot { bool get hasError => error != null; @override - String toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error)'; + String toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error, $stackTrace)'; @override bool operator ==(Object other) { @@ -284,7 +303,8 @@ class AsyncSnapshot { return other is AsyncSnapshot && other.connectionState == connectionState && other.data == data - && other.error == error; + && other.error == error + && other.stackTrace == stackTrace; } @override @@ -318,12 +338,12 @@ typedef AsyncWidgetBuilder = Widget Function(BuildContext context, AsyncSnaps /// of the following snapshots that includes the last one (the one with /// ConnectionState.done): /// -/// * `new AsyncSnapshot.withData(ConnectionState.waiting, null)` -/// * `new AsyncSnapshot.withData(ConnectionState.active, 0)` -/// * `new AsyncSnapshot.withData(ConnectionState.active, 1)` +/// * `AsyncSnapshot.withData(ConnectionState.waiting, null)` +/// * `AsyncSnapshot.withData(ConnectionState.active, 0)` +/// * `AsyncSnapshot.withData(ConnectionState.active, 1)` /// * ... -/// * `new AsyncSnapshot.withData(ConnectionState.active, 9)` -/// * `new AsyncSnapshot.withData(ConnectionState.done, 9)` +/// * `AsyncSnapshot.withData(ConnectionState.active, 9)` +/// * `AsyncSnapshot.withData(ConnectionState.done, 9)` /// /// The actual sequence of invocations of the [builder] depends on the relative /// timing of events produced by the stream and the build rate of the Flutter @@ -332,15 +352,15 @@ typedef AsyncWidgetBuilder = Widget Function(BuildContext context, AsyncSnaps /// Changing the [StreamBuilder] configuration to another stream during event /// generation introduces snapshot pairs of the form: /// -/// * `new AsyncSnapshot.withData(ConnectionState.none, 5)` -/// * `new AsyncSnapshot.withData(ConnectionState.waiting, 5)` +/// * `AsyncSnapshot.withData(ConnectionState.none, 5)` +/// * `AsyncSnapshot.withData(ConnectionState.waiting, 5)` /// /// The latter will be produced only when the new stream is non-null, and the /// former only when the old stream is non-null. /// /// The stream may produce errors, resulting in snapshots of the form: /// -/// * `new AsyncSnapshot.withError(ConnectionState.active, 'some error')` +/// * `AsyncSnapshot.withError(ConnectionState.active, 'some error', someStackTrace)` /// /// The data and error fields of snapshots produced are only changed when the /// state is `ConnectionState.active`. @@ -386,7 +406,11 @@ typedef AsyncWidgetBuilder = Widget Function(BuildContext context, AsyncSnaps /// Padding( /// padding: const EdgeInsets.only(top: 16), /// child: Text('Error: ${snapshot.error}'), -/// ) +/// ), +/// Padding( +/// padding: const EdgeInsets.only(top: 8), +/// child: Text('Stack trace: ${snapshot.stackTrace}'), +/// ), /// ]; /// } else { /// switch (snapshot.connectionState) { @@ -511,8 +535,8 @@ class StreamBuilder extends StreamBuilderBase> { } @override - AsyncSnapshot afterError(AsyncSnapshot current, Object error) { - return AsyncSnapshot.withError(ConnectionState.active, error); + AsyncSnapshot afterError(AsyncSnapshot current, Object error, StackTrace stackTrace) { + return AsyncSnapshot.withError(ConnectionState.active, error, stackTrace); } @override @@ -559,14 +583,14 @@ class StreamBuilder extends StreamBuilderBase> { /// is null, the [builder] will be called with either both or only the latter of /// the following snapshots: /// -/// * `new AsyncSnapshot.withData(ConnectionState.waiting, null)` -/// * `new AsyncSnapshot.withData(ConnectionState.done, 'some data')` +/// * `AsyncSnapshot.withData(ConnectionState.waiting, null)` +/// * `AsyncSnapshot.withData(ConnectionState.done, 'some data')` /// /// If that same future instead completed with an error, the [builder] would be /// called with either both or only the latter of: /// -/// * `new AsyncSnapshot.withData(ConnectionState.waiting, null)` -/// * `new AsyncSnapshot.withError(ConnectionState.done, 'some error')` +/// * `AsyncSnapshot.withData(ConnectionState.waiting, null)` +/// * `AsyncSnapshot.withError(ConnectionState.done, 'some error', someStackTrace)` /// /// The initial snapshot data can be controlled by specifying [initialData]. You /// would use this facility to ensure that if the [builder] is invoked before @@ -579,8 +603,8 @@ class StreamBuilder extends StreamBuilderBase> { /// old future has already completed successfully with data as above, changing /// configuration to a new future results in snapshot pairs of the form: /// -/// * `new AsyncSnapshot.withData(ConnectionState.none, 'data of first future')` -/// * `new AsyncSnapshot.withData(ConnectionState.waiting, 'data of second future')` +/// * `AsyncSnapshot.withData(ConnectionState.none, 'data of first future')` +/// * `AsyncSnapshot.withData(ConnectionState.waiting, 'data of second future')` /// /// In general, the latter will be produced only when the new future is /// non-null, and the former only when the old future is non-null. @@ -768,10 +792,10 @@ class _FutureBuilderState extends State> { _snapshot = AsyncSnapshot.withData(ConnectionState.done, data); }); } - }, onError: (Object error) { + }, onError: (Object error, StackTrace stackTrace) { if (_activeCallbackIdentity == callbackIdentity) { setState(() { - _snapshot = AsyncSnapshot.withError(ConnectionState.done, error); + _snapshot = AsyncSnapshot.withError(ConnectionState.done, error, stackTrace); }); } }); diff --git a/packages/flutter/test/widgets/async_test.dart b/packages/flutter/test/widgets/async_test.dart index 31d6756003..b0db48b924 100644 --- a/packages/flutter/test/widgets/async_test.dart +++ b/packages/flutter/test/widgets/async_test.dart @@ -34,9 +34,20 @@ void main() { expect(const AsyncSnapshot.nothing().connectionState, ConnectionState.none); expect(const AsyncSnapshot.nothing().data, isNull); expect(const AsyncSnapshot.nothing().error, isNull); + expect(const AsyncSnapshot.nothing().stackTrace, isNull); expect(const AsyncSnapshot.waiting().connectionState, ConnectionState.waiting); expect(const AsyncSnapshot.waiting().data, isNull); expect(const AsyncSnapshot.waiting().error, isNull); + expect(const AsyncSnapshot.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.withError(ConnectionState.done, error), + AsyncSnapshot.withError( + ConnectionState.done, error, StackTrace.empty)); }); }); group('Async smoke tests', () { @@ -67,12 +78,12 @@ void main() { await tester.pumpWidget(FutureBuilder( key: key, future: null, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.none, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, null, null, null)'), findsOneWidget); final Completer completer = Completer(); await tester.pumpWidget(FutureBuilder( key: key, future: completer.future, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); }); testWidgets('gracefully handles transition to null future', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -80,14 +91,14 @@ void main() { await tester.pumpWidget(FutureBuilder( key: key, future: completer.future, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); await tester.pumpWidget(FutureBuilder( key: key, future: null, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.none, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, null, null, null)'), findsOneWidget); completer.complete('hello'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.none, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, null, null, null)'), findsOneWidget); }); testWidgets('gracefully handles transition to other future', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -96,35 +107,35 @@ void main() { await tester.pumpWidget(FutureBuilder( key: key, future: completerA.future, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); await tester.pumpWidget(FutureBuilder( key: key, future: completerB.future, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); completerB.complete('B'); completerA.complete('A'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, B, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.done, B, null, null)'), findsOneWidget); }); testWidgets('tracks life-cycle of Future to success', (WidgetTester tester) async { final Completer completer = Completer(); await tester.pumpWidget(FutureBuilder( future: completer.future, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); completer.complete('hello'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, hello, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.done, hello, null, null)'), findsOneWidget); }); testWidgets('tracks life-cycle of Future to error', (WidgetTester tester) async { final Completer completer = Completer(); await tester.pumpWidget(FutureBuilder( future: completer.future, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); - completer.completeError('bad'); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); + completer.completeError('bad', StackTrace.fromString('trace')); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, null, bad)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.done, null, bad, trace)'), findsOneWidget); }); testWidgets('runs the builder using given initial data', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -134,7 +145,7 @@ void main() { builder: snapshotText, initialData: 'I', )); - expect(find.text('AsyncSnapshot(ConnectionState.none, I, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, I, null, null)'), findsOneWidget); }); testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -144,7 +155,7 @@ void main() { builder: snapshotText, initialData: 'I', )); - expect(find.text('AsyncSnapshot(ConnectionState.none, I, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, I, null, null)'), findsOneWidget); final Completer completer = Completer(); await tester.pumpWidget(FutureBuilder( key: key, @@ -152,7 +163,7 @@ void main() { builder: snapshotText, initialData: 'Ignored', )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null, null)'), findsOneWidget); }); }); group('StreamBuilder', () { @@ -161,12 +172,12 @@ void main() { await tester.pumpWidget(StreamBuilder( key: key, stream: null, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.none, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, null, null, null)'), findsOneWidget); final StreamController controller = StreamController(); await tester.pumpWidget(StreamBuilder( key: key, stream: controller.stream, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); }); testWidgets('gracefully handles transition to null stream', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -174,11 +185,11 @@ void main() { await tester.pumpWidget(StreamBuilder( key: key, stream: controller.stream, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); await tester.pumpWidget(StreamBuilder( key: key, stream: null, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.none, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, null, null, null)'), findsOneWidget); }); testWidgets('gracefully handles transition to other stream', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -187,14 +198,14 @@ void main() { await tester.pumpWidget(StreamBuilder( key: key, stream: controllerA.stream, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); await tester.pumpWidget(StreamBuilder( key: key, stream: controllerB.stream, builder: snapshotText, )); controllerB.add('B'); controllerA.add('A'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.active, B, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.active, B, null, null)'), findsOneWidget); }); testWidgets('tracks events and errors of stream until completion', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -202,19 +213,19 @@ void main() { await tester.pumpWidget(StreamBuilder( key: key, stream: controller.stream, builder: snapshotText, )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsOneWidget); controller.add('1'); controller.add('2'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.active, 2, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.active, 2, null, null)'), findsOneWidget); controller.add('3'); - controller.addError('bad'); + controller.addError('bad', StackTrace.fromString('trace')); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.active, null, bad)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.active, null, bad, trace)'), findsOneWidget); controller.add('4'); controller.close(); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, 4, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.done, 4, null, null)'), findsOneWidget); }); testWidgets('runs the builder using given initial data', (WidgetTester tester) async { final StreamController controller = StreamController(); @@ -223,7 +234,7 @@ void main() { builder: snapshotText, initialData: 'I', )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null, null)'), findsOneWidget); }); testWidgets('ignores initialData when reconfiguring', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); @@ -233,7 +244,7 @@ void main() { builder: snapshotText, initialData: 'I', )); - expect(find.text('AsyncSnapshot(ConnectionState.none, I, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.none, I, null, null)'), findsOneWidget); final StreamController controller = StreamController(); await tester.pumpWidget(StreamBuilder( key: key, @@ -241,7 +252,7 @@ void main() { builder: snapshotText, initialData: 'Ignored', )); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null)'), findsOneWidget); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null, null)'), findsOneWidget); }); }); group('FutureBuilder and StreamBuilder behave identically on Stream from Future', () { @@ -251,35 +262,46 @@ void main() { FutureBuilder(future: completer.future, builder: snapshotText), StreamBuilder(stream: completer.future.asStream(), builder: snapshotText), ])); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsNWidgets(2)); completer.complete('hello'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, hello, null)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(ConnectionState.done, hello, null, null)'), findsNWidgets(2)); }); - testWidgets('when completing with error', (WidgetTester tester) async { + testWidgets('when completing with error and with empty stack trace', (WidgetTester tester) async { final Completer completer = Completer(); await tester.pumpWidget(Column(children: [ FutureBuilder(future: completer.future, builder: snapshotText), StreamBuilder(stream: completer.future.asStream(), builder: snapshotText), ])); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null)'), findsNWidgets(2)); - completer.completeError('bad'); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsNWidgets(2)); + completer.completeError('bad', StackTrace.empty); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, null, bad)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(ConnectionState.done, null, bad, )'), findsNWidgets(2)); + }); + testWidgets('when completing with error and with stack trace', (WidgetTester tester) async { + final Completer completer = Completer(); + await tester.pumpWidget(Column(children: [ + FutureBuilder(future: completer.future, builder: snapshotText), + StreamBuilder(stream: completer.future.asStream(), builder: snapshotText), + ])); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, null, null, null)'), findsNWidgets(2)); + completer.completeError('bad', StackTrace.fromString('trace')); + await eventFiring(tester); + expect(find.text('AsyncSnapshot(ConnectionState.done, null, bad, trace)'), findsNWidgets(2)); }); testWidgets('when Future is null', (WidgetTester tester) async { await tester.pumpWidget(Column(children: [ FutureBuilder(future: null, builder: snapshotText), StreamBuilder(stream: null, builder: snapshotText), ])); - expect(find.text('AsyncSnapshot(ConnectionState.none, null, null)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(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: [ FutureBuilder(future: null, builder: snapshotText, initialData: 'I'), StreamBuilder(stream: null, builder: snapshotText, initialData: 'I'), ])); - expect(find.text('AsyncSnapshot(ConnectionState.none, I, null)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(ConnectionState.none, I, null, null)'), findsNWidgets(2)); }); testWidgets('when using initialData and completing with data', (WidgetTester tester) async { final Completer completer = Completer(); @@ -287,10 +309,10 @@ void main() { FutureBuilder(future: completer.future, builder: snapshotText, initialData: 'I'), StreamBuilder(stream: completer.future.asStream(), builder: snapshotText, initialData: 'I'), ])); - expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(ConnectionState.waiting, I, null, null)'), findsNWidgets(2)); completer.complete('hello'); await eventFiring(tester); - expect(find.text('AsyncSnapshot(ConnectionState.done, hello, null)'), findsNWidgets(2)); + expect(find.text('AsyncSnapshot(ConnectionState.done, hello, null, null)'), findsNWidgets(2)); }); }); group('StreamBuilderBase', () { @@ -326,11 +348,11 @@ void main() { final StreamController controller = StreamController(); await tester.pumpWidget(StringCollector(key: key, stream: controller.stream)); controller.add('1'); - controller.addError('bad'); + controller.addError('bad', StackTrace.fromString('trace')); controller.add('2'); controller.close(); await eventFiring(tester); - expect(find.text('conn, data:1, error:bad, data:2, done'), findsOneWidget); + expect(find.text('conn, data:1, error:bad stackTrace:trace, data:2, done'), findsOneWidget); }); }); } @@ -352,7 +374,7 @@ class StringCollector extends StreamBuilderBase> { List afterData(List current, String data) => current..add('data:$data'); @override - List afterError(List current, dynamic error) => current..add('error:$error'); + List afterError(List current, dynamic error, StackTrace stackTrace) => current..add('error:$error stackTrace:$stackTrace'); @override List afterDone(List current) => current..add('done');