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 {
/// 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<T, S> extends State<StreamBuilderBase<T, S>> {
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<T> {
/// 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<T> {
/// 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<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error);
/// The [data], [error], and [stackTrace] fields persist unmodified, even if
/// the new state is [ConnectionState.none].
AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error, stackTrace);
/// Returns whether this snapshot contains a non-null [data] value.
///
......@@ -275,7 +294,7 @@ class AsyncSnapshot<T> {
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<T> {
return other is AsyncSnapshot<T>
&& other.connectionState == connectionState
&& other.data == data
&& other.error == error;
&& other.error == error
&& other.stackTrace == stackTrace;
}
@override
......@@ -318,12 +338,12 @@ typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnaps
/// of the following snapshots that includes the last one (the one with
/// ConnectionState.done):
///
/// * `new AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
/// * ...
/// * `new AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
/// * `new AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
/// * `AsyncSnapshot<int>.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<T> = Widget Function(BuildContext context, AsyncSnaps
/// Changing the [StreamBuilder] configuration to another stream during event
/// generation introduces snapshot pairs of the form:
///
/// * `new AsyncSnapshot<int>.withData(ConnectionState.none, 5)`
/// * `new AsyncSnapshot<int>.withData(ConnectionState.waiting, 5)`
/// * `AsyncSnapshot<int>.withData(ConnectionState.none, 5)`
/// * `AsyncSnapshot<int>.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<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
/// state is `ConnectionState.active`.
......@@ -386,7 +406,11 @@ typedef AsyncWidgetBuilder<T> = 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<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
}
@override
AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error) {
return AsyncSnapshot<T>.withError(ConnectionState.active, error);
AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {
return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);
}
@override
......@@ -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
/// the following snapshots:
///
/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `new AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<String>.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<String>.withData(ConnectionState.waiting, null)`
/// * `new AsyncSnapshot<String>.withError(ConnectionState.done, 'some error')`
/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
/// * `AsyncSnapshot<String>.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<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
/// 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<String>.withData(ConnectionState.none, 'data of first future')`
/// * `new AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
/// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
/// * `AsyncSnapshot<String>.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<T> extends State<FutureBuilder<T>> {
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
});
}
}, onError: (Object error) {
}, onError: (Object error, StackTrace stackTrace) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error);
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
});
}
});
......
......@@ -34,9 +34,20 @@ void main() {
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, StackTrace.empty));
});
});
group('Async smoke tests', () {
......@@ -67,12 +78,12 @@ void main() {
await tester.pumpWidget(FutureBuilder<String>(
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>();
await tester.pumpWidget(FutureBuilder<String>(
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 {
final GlobalKey key = GlobalKey();
......@@ -80,14 +91,14 @@ void main() {
await tester.pumpWidget(FutureBuilder<String>(
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>(
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');
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 {
final GlobalKey key = GlobalKey();
......@@ -96,35 +107,35 @@ void main() {
await tester.pumpWidget(FutureBuilder<String>(
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>(
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');
completerA.complete('A');
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 {
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)'), findsOneWidget);
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)'), findsOneWidget);
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)'), findsOneWidget);
completer.completeError('bad');
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)'), findsOneWidget);
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();
......@@ -134,7 +145,7 @@ void main() {
builder: snapshotText,
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 {
final GlobalKey key = GlobalKey();
......@@ -144,7 +155,7 @@ void main() {
builder: snapshotText,
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>();
await tester.pumpWidget(FutureBuilder<String>(
key: key,
......@@ -152,7 +163,7 @@ void main() {
builder: snapshotText,
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', () {
......@@ -161,12 +172,12 @@ void main() {
await tester.pumpWidget(StreamBuilder<String>(
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>();
await tester.pumpWidget(StreamBuilder<String>(
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 {
final GlobalKey key = GlobalKey();
......@@ -174,11 +185,11 @@ void main() {
await tester.pumpWidget(StreamBuilder<String>(
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>(
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 {
final GlobalKey key = GlobalKey();
......@@ -187,14 +198,14 @@ void main() {
await tester.pumpWidget(StreamBuilder<String>(
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>(
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)'), findsOneWidget);
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();
......@@ -202,19 +213,19 @@ void main() {
await tester.pumpWidget(StreamBuilder<String>(
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('2');
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.addError('bad');
controller.addError('bad', StackTrace.fromString('trace'));
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.close();
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 {
final StreamController<String> controller = StreamController<String>();
......@@ -223,7 +234,7 @@ void main() {
builder: snapshotText,
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 {
final GlobalKey key = GlobalKey();
......@@ -233,7 +244,7 @@ void main() {
builder: snapshotText,
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>();
await tester.pumpWidget(StreamBuilder<String>(
key: key,
......@@ -241,7 +252,7 @@ void main() {
builder: snapshotText,
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', () {
......@@ -251,35 +262,46 @@ void main() {
FutureBuilder<String>(future: completer.future, 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');
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>();
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)'), findsNWidgets(2));
completer.completeError('bad');
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)'), findsNWidgets(2));
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>(future: 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 {
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));
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>();
......@@ -287,10 +309,10 @@ void main() {
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));
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)'), findsNWidgets(2));
expect(find.text('AsyncSnapshot<String>(ConnectionState.done, hello, null, null)'), findsNWidgets(2));
});
});
group('StreamBuilderBase', () {
......@@ -326,11 +348,11 @@ void main() {
final StreamController<String> controller = StreamController<String>();
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<String, List<String>> {
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');
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');
......
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