Migrate to runZonedGuarded (#93)

The `onError` argument to `runZoned` is deprecated. Switch to the
supported `runZonedGuarded`.

Remove argument types on the function literal since thy can now be
inferred. `runZonedGuarded` has a specific function type argument,
whereas `onError` was typed as `Function` which did not allow inference
on argument types.

Refactor handling of `!when` since the new API doesn't allow a nullable
callback.

Refactor some tests to use `having` matchers which previously relied on
calling methods on `dynamic`.
diff --git a/lib/src/chain.dart b/lib/src/chain.dart
index fef2c2d..b685be9 100644
--- a/lib/src/chain.dart
+++ b/lib/src/chain.dart
@@ -66,10 +66,10 @@
   /// parent Zone's `unhandledErrorHandler` will be called with the error and
   /// its chain.
   ///
-  /// If [errorZone] is `true`, the zone this creates will be an error zone,
-  /// even if [onError] isn't passed. This means that any errors that would
-  /// cross the zone boundary are considered unhandled. If [errorZone] is
-  /// `false`, [onError] must be `null`.
+  /// The zone this creates will be an error zone if either [onError] is
+  /// not `null` and [when] is false,
+  /// or if both [when] and [errorZone] are `true`.
+  ///  If [errorZone] is `false`, [onError] must be `null`.
   ///
   /// If [callback] returns a value, it will be returned by [capture] as well.
   static T capture<T>(T Function() callback,
@@ -82,20 +82,10 @@
     }
 
     if (!when) {
-      void Function(Object, StackTrace)? newOnError;
-      if (onError != null) {
-        void wrappedOnError(Object error, StackTrace? stackTrace) {
-          onError(
-              error,
-              stackTrace == null
-                  ? Chain.current()
-                  : Chain.forTrace(stackTrace));
-        }
-
-        newOnError = wrappedOnError;
-      }
-
-      return runZoned(callback, onError: newOnError);
+      if (onError == null) return runZoned(callback);
+      return runZonedGuarded(callback, (error, stackTrace) {
+        onError(error, Chain.forTrace(stackTrace));
+      }) as T;
     }
 
     var spec = StackZoneSpecification(onError, errorZone: errorZone);
diff --git a/test/chain/chain_test.dart b/test/chain/chain_test.dart
index 3309ae1..bb28b79 100644
--- a/test/chain/chain_test.dart
+++ b/test/chain/chain_test.dart
@@ -64,10 +64,10 @@
     });
 
     test('with no onError blocks errors', () {
-      runZoned(() {
+      runZonedGuarded(() {
         var future = Chain.capture(() => Future.error('oh no'), when: false);
         future.then(expectAsync1((_) {}, count: 0));
-      }, onError: expectAsync2((error, chain) {
+      }, expectAsync2((error, chain) {
         expect(error, equals('oh no'));
         expect(chain, isA<Chain>());
       }));
diff --git a/test/chain/dart2js_test.dart b/test/chain/dart2js_test.dart
index c0ec913..f708637 100644
--- a/test/chain/dart2js_test.dart
+++ b/test/chain/dart2js_test.dart
@@ -140,7 +140,7 @@
     test('and relays them to the parent zone', () {
       var completer = Completer();
 
-      runZoned(() {
+      runZonedGuarded(() {
         Chain.capture(() {
           inMicrotask(() => throw 'error');
         }, onError: (error, chain) {
@@ -148,11 +148,11 @@
           expect(chain.traces, hasLength(2));
           throw error;
         });
-      }, onError: (error, chain) {
+      }, (error, chain) {
         try {
           expect(error, equals('error'));
-          expect(chain, isA<Chain>());
-          expect(chain.traces, hasLength(2));
+          expect(chain,
+              isA<Chain>().having((c) => c.traces, 'traces', hasLength(2)));
           completer.complete();
         } on Object catch (error, stackTrace) {
           completer.completeError(error, stackTrace);
@@ -166,13 +166,13 @@
   test('capture() without onError passes exceptions to parent zone', () {
     var completer = Completer();
 
-    runZoned(() {
+    runZonedGuarded(() {
       Chain.capture(() => inMicrotask(() => throw 'error'));
-    }, onError: (error, chain) {
+    }, (error, chain) {
       try {
         expect(error, equals('error'));
-        expect(chain, isA<Chain>());
-        expect(chain.traces, hasLength(2));
+        expect(chain,
+            isA<Chain>().having((c) => c.traces, 'traces', hasLength(2)));
         completer.complete();
       } on Object catch (error, stackTrace) {
         completer.completeError(error, stackTrace);
diff --git a/test/chain/vm_test.dart b/test/chain/vm_test.dart
index 273276e..8a66b83 100644
--- a/test/chain/vm_test.dart
+++ b/test/chain/vm_test.dart
@@ -234,7 +234,7 @@
     test('and relays them to the parent zone', () {
       var completer = Completer();
 
-      runZoned(() {
+      runZonedGuarded(() {
         Chain.capture(() {
           inMicrotask(() => throw 'error');
         }, onError: (error, chain) {
@@ -243,12 +243,13 @@
               contains(frameMember(startsWith('inMicrotask'))));
           throw error;
         });
-      }, onError: (error, chain) {
+      }, (error, chain) {
         try {
           expect(error, equals('error'));
-          expect(chain, isA<Chain>());
-          expect(chain.traces[1].frames,
-              contains(frameMember(startsWith('inMicrotask'))));
+          expect(
+              chain,
+              isA<Chain>().having((c) => c.traces[1].frames, 'traces[1].frames',
+                  contains(frameMember(startsWith('inMicrotask')))));
           completer.complete();
         } on Object catch (error, stackTrace) {
           completer.completeError(error, stackTrace);
@@ -262,14 +263,15 @@
   test('capture() without onError passes exceptions to parent zone', () {
     var completer = Completer();
 
-    runZoned(() {
+    runZonedGuarded(() {
       Chain.capture(() => inMicrotask(() => throw 'error'));
-    }, onError: (error, chain) {
+    }, (error, chain) {
       try {
         expect(error, equals('error'));
-        expect(chain, isA<Chain>());
-        expect(chain.traces[1].frames,
-            contains(frameMember(startsWith('inMicrotask'))));
+        expect(
+            chain,
+            isA<Chain>().having((c) => c.traces[1].frames, 'traces[1].frames',
+                contains(frameMember(startsWith('inMicrotask')))));
         completer.complete();
       } on Object catch (error, stackTrace) {
         completer.completeError(error, stackTrace);