diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 0bddcadb2c..5808af6386 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -1361,7 +1361,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { final Zone realAsyncZone = Zone.current.fork( specification: ZoneSpecification( scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void Function() f) { - Zone.root.scheduleMicrotask(f); + _rootDelegate.scheduleMicrotask(zone, f); }, createTimer: ( Zone self, @@ -1370,7 +1370,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { Duration duration, void Function() f, ) { - return Zone.root.createTimer(duration, f); + return _rootDelegate.createTimer(zone, duration, f); }, createPeriodicTimer: ( Zone self, @@ -1379,7 +1379,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { Duration period, void Function(Timer timer) f, ) { - return Zone.root.createPeriodicTimer(period, f); + return _rootDelegate.createPeriodicTimer(zone, period, f); }, ), ); @@ -1442,6 +1442,26 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { _currentFakeAsync!.flushMicrotasks(); } + /// The [ZoneDelegate] for [Zone.root]. + /// + /// Used to schedule (real) microtasks and timers in the root zone, + /// to be run in the correct zone. + static final ZoneDelegate _rootDelegate = _captureRootZoneDelegate(); + + /// Hack to extract the [ZoneDelegate] for [Zone.root]. + static ZoneDelegate _captureRootZoneDelegate() { + final Zone captureZone = Zone.root.fork( + specification: ZoneSpecification( + run: (Zone self, ZoneDelegate parent, Zone zone, R Function() f) { + return parent as R; + }, + ), + ); + // The `_captureRootZoneDelegate` argument just happens to be a constant + // function with the necessary type. It's not called recursively. + return captureZone.run(_captureRootZoneDelegate); + } + @override void scheduleAttachRootWidget(Widget rootWidget) { // We override the default version of this so that the application-startup widget tree diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 206d416d81..297ada1fac 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -406,6 +406,82 @@ void main() { expect(result, isNull); expect(tester.takeException(), isNotNull); }); + + testWidgets('runs in original zone', (WidgetTester tester) async { + final Zone testZone = Zone.current; + Zone? runAsyncZone; + Zone? timerZone; + Zone? periodicTimerZone; + Zone? microtaskZone; + + Zone? innerZone; + Zone? innerTimerZone; + Zone? innerPeriodicTimerZone; + Zone? innerMicrotaskZone; + + await tester.binding.runAsync(() async { + final Zone currentZone = Zone.current; + runAsyncZone = currentZone; + + // Complete a future when all callbacks have completed. + int pendingCallbacks = 6; + final Completer callbacksDone = Completer(); + void onCallback() { + if (--pendingCallbacks == 0) { + testZone.run(() { + callbacksDone.complete(null); + }); + } + } + + // On the runAsync zone itself. + currentZone.createTimer(Duration.zero, () { + timerZone = Zone.current; + onCallback(); + }); + currentZone.createPeriodicTimer(Duration.zero, (Timer timer) { + timer.cancel(); + periodicTimerZone = Zone.current; + onCallback(); + }); + currentZone.scheduleMicrotask(() { + microtaskZone = Zone.current; + onCallback(); + }); + + // On a nested user-created zone. + final Zone inner = runZoned(() => Zone.current); + innerZone = inner; + inner.createTimer(Duration.zero, () { + innerTimerZone = Zone.current; + onCallback(); + }); + inner.createPeriodicTimer(Duration.zero, (Timer timer) { + timer.cancel(); + innerPeriodicTimerZone = Zone.current; + onCallback(); + }); + inner.scheduleMicrotask(() { + innerMicrotaskZone = Zone.current; + onCallback(); + }); + + await callbacksDone.future; + }); + expect(runAsyncZone, isNotNull); + expect(timerZone, same(runAsyncZone)); + expect(periodicTimerZone, same(runAsyncZone)); + expect(microtaskZone, same(runAsyncZone)); + + expect(innerZone, isNotNull); + expect(innerTimerZone, same(innerZone)); + expect(innerPeriodicTimerZone, same(innerZone)); + expect(innerMicrotaskZone, same(innerZone)); + + expect(runAsyncZone, isNot(same(testZone))); + expect(runAsyncZone, isNot(same(innerZone))); + expect(innerZone, isNot(same(testZone))); + }); }); group('showKeyboard', () {