Unverified Commit 85f0aea8 authored by creativecreatorormaybenot's avatar creativecreatorormaybenot Committed by GitHub

Add stackTrace to AsyncSnapshot (#69507)

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