diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 217828f6dabe..6aa7ba300e1a 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -4711,12 +4711,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext { owner!._debugCurrentBuildTarget = this; return true; }()); - performRebuild(); - assert(() { - assert(owner!._debugCurrentBuildTarget == this); - owner!._debugCurrentBuildTarget = debugPreviousBuildTarget; - return true; - }()); + try { + performRebuild(); + } finally { + assert(() { + assert(owner!._debugCurrentBuildTarget == this); + owner!._debugCurrentBuildTarget = debugPreviousBuildTarget; + return true; + }()); + } assert(!_dirty); } diff --git a/packages/flutter/test/widgets/error_widget_test.dart b/packages/flutter/test/widgets/error_widget_test.dart new file mode 100644 index 000000000000..b91bfdc24d0c --- /dev/null +++ b/packages/flutter/test/widgets/error_widget_test.dart @@ -0,0 +1,92 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('ErrorWidget displays actual error when throwing during build', (WidgetTester tester) async { + final Key container = UniqueKey(); + const String errorText = 'Oh no, there was a crash!!1'; + + await tester.pumpWidget( + Container( + key: container, + color: Colors.red, + padding: const EdgeInsets.all(10), + child: Builder( + builder: (BuildContext context) { + throw UnsupportedError(errorText); + }, + ), + ), + ); + + expect( + tester.takeException(), + isA().having((UnsupportedError error) => error.message, 'message', contains(errorText)), + ); + + final ErrorWidget errorWidget = tester.widget(find.byType(ErrorWidget)); + expect(errorWidget.message, contains(errorText)); + + // Failure in one widget shouldn't ripple through the entire tree and effect + // ancestors. Those should still be in the tree. + expect(find.byKey(container), findsOneWidget); + }); + + testWidgets('when constructing an ErrorWidget due to a build failure throws an error, fail gracefully', (WidgetTester tester) async { + final Key container = UniqueKey(); + await tester.pumpWidget( + Container( + key: container, + color: Colors.red, + padding: const EdgeInsets.all(10), + // This widget throws during build, which causes the construction of an + // ErrorWidget with the build error. However, during construction of + // that ErrorWidget, another error is thrown. + child: const MyDoubleThrowingWidget(), + ), + ); + + expect( + tester.takeException(), + isA().having((UnsupportedError error) => error.message, 'message', contains(MyThrowingElement.debugFillPropertiesErrorMessage)), + ); + + final ErrorWidget errorWidget = tester.widget(find.byType(ErrorWidget)); + expect(errorWidget.message, contains(MyThrowingElement.debugFillPropertiesErrorMessage)); + + // Failure in one widget shouldn't ripple through the entire tree and effect + // ancestors. Those should still be in the tree. + expect(find.byKey(container), findsOneWidget); + }); +} + +// This widget throws during its regular build and then again when the +// ErrorWidget is constructed, which calls MyThrowingElement.debugFillProperties. +class MyDoubleThrowingWidget extends StatelessWidget { + const MyDoubleThrowingWidget({super.key}); + + @override + StatelessElement createElement() => MyThrowingElement(this); + + @override + Widget build(BuildContext context) { + throw UnsupportedError('You cannot build me!'); + } +} + +class MyThrowingElement extends StatelessElement { + MyThrowingElement(super.widget); + + static const String debugFillPropertiesErrorMessage = 'Crash during debugFillProperties'; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + throw UnsupportedError(debugFillPropertiesErrorMessage); + } +}