diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index 6847d1433fe6a..5d50c0ef22a15 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -1605,12 +1605,29 @@ abstract class DiagnosticsNode { /// by this method and interactive tree views in the Flutter IntelliJ /// plugin. @mustCallSuper - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { Map result = {}; assert(() { final bool hasChildren = getChildren().isNotEmpty; - result = { + final Map essentialDetails = { 'description': toDescription(), + 'shouldIndent': style != DiagnosticsTreeStyle.flat && + style != DiagnosticsTreeStyle.error, + ...delegate.additionalNodeProperties(this, fullDetails: fullDetails), + if (delegate.subtreeDepth > 0) + 'children': toJsonList( + delegate.filterChildren(getChildren(), this), + this, + delegate, + fullDetails: fullDetails, + ), + }; + + result = !fullDetails ? essentialDetails : { + ...essentialDetails, 'type': runtimeType.toString(), if (name != null) 'name': name, @@ -1634,18 +1651,12 @@ abstract class DiagnosticsNode { 'allowWrap': allowWrap, if (allowNameWrap) 'allowNameWrap': allowNameWrap, - ...delegate.additionalNodeProperties(this), if (delegate.includeProperties) 'properties': toJsonList( delegate.filterProperties(getProperties(), this), this, delegate, - ), - if (delegate.subtreeDepth > 0) - 'children': toJsonList( - delegate.filterChildren(getChildren(), this), - this, - delegate, + fullDetails: fullDetails, ), }; return true; @@ -1661,8 +1672,9 @@ abstract class DiagnosticsNode { static List> toJsonList( List? nodes, DiagnosticsNode? parent, - DiagnosticsSerializationDelegate delegate, - ) { + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { bool truncated = false; if (nodes == null) { return const >[]; @@ -1674,7 +1686,10 @@ abstract class DiagnosticsNode { truncated = true; } final List> json = nodes.map>((DiagnosticsNode node) { - return node.toJsonMap(delegate.delegateForNode(node)); + return node.toJsonMap( + delegate.delegateForNode(node), + fullDetails: fullDetails, + ); }).toList(); if (truncated) { json.last['truncated'] = true; @@ -1857,8 +1872,17 @@ class StringProperty extends DiagnosticsProperty { final bool quoted; @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } json['quoted'] = quoted; return json; } @@ -1913,8 +1937,18 @@ abstract class _NumProperty extends DiagnosticsProperty { }) : super.lazy(); @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } + if (unit != null) { json['unit'] = unit; } @@ -2097,8 +2131,17 @@ class FlagProperty extends DiagnosticsProperty { ); @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (ifTrue != null) { json['ifTrue'] = ifTrue; } @@ -2219,8 +2262,17 @@ class IterableProperty extends DiagnosticsProperty> { } @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (value != null) { json['values'] = value!.map((T value) => value.toString()).toList(); } @@ -2357,8 +2409,17 @@ class ObjectFlagProperty extends DiagnosticsProperty { } @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (ifPresent != null) { json['ifPresent'] = ifPresent; } @@ -2435,8 +2496,17 @@ class FlagsSummary extends DiagnosticsProperty> { } @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (value.isNotEmpty) { json['values'] = _formattedValues().toList(); } @@ -2555,7 +2625,10 @@ class DiagnosticsProperty extends DiagnosticsNode { final bool allowNameWrap; @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { final T? v = value; List>? properties; if (delegate.expandPropertyValues && delegate.includeProperties && v is Diagnosticable && getProperties().isEmpty) { @@ -2565,9 +2638,16 @@ class DiagnosticsProperty extends DiagnosticsNode { delegate.filterProperties(v.toDiagnosticsNode().getProperties(), this), this, delegate, + fullDetails: fullDetails, ); } - final Map json = super.toJsonMap(delegate); + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (properties != null) { json['properties'] = properties; } @@ -3503,7 +3583,10 @@ abstract class DiagnosticsSerializationDelegate { /// /// This method is called for every [DiagnosticsNode] that's included in /// the serialization. - Map additionalNodeProperties(DiagnosticsNode node); + Map additionalNodeProperties( + DiagnosticsNode node, { + bool fullDetails = true, + }); /// Filters the list of [DiagnosticsNode]s that will be included as children /// for the given `owner` node. @@ -3595,7 +3678,10 @@ class _DefaultDiagnosticsSerializationDelegate implements DiagnosticsSerializati }); @override - Map additionalNodeProperties(DiagnosticsNode node) { + Map additionalNodeProperties( + DiagnosticsNode node, { + bool fullDetails = true, + }) { return const {}; } diff --git a/packages/flutter/lib/src/painting/colors.dart b/packages/flutter/lib/src/painting/colors.dart index a897c0b33d660..b74862657687a 100644 --- a/packages/flutter/lib/src/painting/colors.dart +++ b/packages/flutter/lib/src/painting/colors.dart @@ -495,8 +495,17 @@ class ColorProperty extends DiagnosticsProperty { }); @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (value != null) { json['valueProperties'] = { 'red': value!.red, diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index a1553a2ba121e..d62671075c211 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -5379,13 +5379,18 @@ class _ElementDiagnosticableTreeNode extends DiagnosticableTreeNode { final bool stateful; @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap(delegate, fullDetails: fullDetails,); final Element element = value as Element; if (!element.debugIsDefunct) { json['widgetRuntimeType'] = element.widget.runtimeType.toString(); } - json['stateful'] = stateful; + if (fullDetails) { + json['stateful'] = stateful; + } return json; } } diff --git a/packages/flutter/lib/src/widgets/icon_data.dart b/packages/flutter/lib/src/widgets/icon_data.dart index ca118126d1a5f..d13ce97863204 100644 --- a/packages/flutter/lib/src/widgets/icon_data.dart +++ b/packages/flutter/lib/src/widgets/icon_data.dart @@ -121,8 +121,17 @@ class IconDataProperty extends DiagnosticsProperty { }); @override - Map toJsonMap(DiagnosticsSerializationDelegate delegate) { - final Map json = super.toJsonMap(delegate); + Map toJsonMap( + DiagnosticsSerializationDelegate delegate, { + bool fullDetails = true, + }) { + final Map json = super.toJsonMap( + delegate, + fullDetails: fullDetails, + ); + if (!fullDetails) { + return json; + } if (value != null) { json['valueProperties'] = { 'codePoint': value!.codePoint, diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index d233170fdccd6..072b94e9209f2 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -1729,9 +1729,11 @@ mixin WidgetInspectorService { Map? _nodeToJson( DiagnosticsNode? node, - InspectorSerializationDelegate delegate, + InspectorSerializationDelegate delegate, { + bool fullDetails = true, + } ) { - return node?.toJsonMap(delegate); + return node?.toJsonMap(delegate, fullDetails: fullDetails); } bool _isValueCreatedByLocalProject(Object? value) { @@ -1801,8 +1803,14 @@ mixin WidgetInspectorService { List nodes, InspectorSerializationDelegate delegate, { required DiagnosticsNode? parent, + bool fullDetails = true, }) { - return DiagnosticsNode.toJsonList(nodes, parent, delegate); + return DiagnosticsNode.toJsonList( + nodes, + parent, + delegate, + fullDetails: fullDetails, + ); } /// Returns a JSON representation of the properties of the [DiagnosticsNode] @@ -1976,11 +1984,14 @@ mixin WidgetInspectorService { final String groupName = parameters['groupName']!; final bool isSummaryTree = parameters['isSummaryTree'] == 'true'; final bool withPreviews = parameters['withPreviews'] == 'true'; + // If the "fullDetails" parameter is not provided, default to true. + final bool fullDetails = parameters['fullDetails'] != 'false'; final Map? result = _getRootWidgetTreeImpl( groupName: groupName, isSummaryTree: isSummaryTree, withPreviews: withPreviews, + fullDetails: fullDetails, ); return Future>.value({ @@ -1992,6 +2003,7 @@ mixin WidgetInspectorService { required String groupName, required bool isSummaryTree, required bool withPreviews, + bool fullDetails = true, Map? Function( DiagnosticsNode, InspectorSerializationDelegate)? addAdditionalPropertiesCallback, @@ -2032,6 +2044,7 @@ mixin WidgetInspectorService { ? combinedAddAdditionalPropertiesCallback : null, ), + fullDetails: fullDetails, ); } @@ -3788,19 +3801,24 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate bool get _interactive => groupName != null; @override - Map additionalNodeProperties(DiagnosticsNode node) { + Map additionalNodeProperties( + DiagnosticsNode node, { + bool fullDetails = true, + }) { final Map result = {}; final Object? value = node.value; + if (summaryTree && fullDetails) { + result['summaryTree'] = true; + } if (_interactive) { result['valueId'] = service.toId(value, groupName!); } - if (summaryTree) { - result['summaryTree'] = true; - } final _Location? creationLocation = _getCreationLocation(value); if (creationLocation != null) { - result['locationId'] = _toLocationId(creationLocation); - result['creationLocation'] = creationLocation.toJsonMap(); + if (fullDetails) { + result['locationId'] = _toLocationId(creationLocation); + result['creationLocation'] = creationLocation.toJsonMap(); + } if (service._isLocalCreationLocation(creationLocation.file)) { _nodesCreatedByLocalProject.add(node); result['createdByLocalProject'] = true; diff --git a/packages/flutter/test/foundation/diagnostics_json_test.dart b/packages/flutter/test/foundation/diagnostics_json_test.dart index 53bad0e833d91..867605d62c67d 100644 --- a/packages/flutter/test/foundation/diagnostics_json_test.dart +++ b/packages/flutter/test/foundation/diagnostics_json_test.dart @@ -24,6 +24,16 @@ void main() { }); group('Serialization', () { + const List essentialDiagnosticKeys = [ + 'description', + 'shouldIndent', + ]; + const List detailedDiagnosticKeys = [ + 'type', + 'hasChildren', + 'allowWrap', + ]; + final TestTree testTree = TestTree( properties: [ StringProperty('stringProperty1', 'value1', quoted: false), @@ -70,6 +80,46 @@ void main() { final Map result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate()); expect(result.containsKey('properties'), isFalse); expect(result.containsKey('children'), isFalse); + + for (final String keyName in essentialDiagnosticKeys) { + expect( + result.containsKey(keyName), + isTrue, + reason: '$keyName is included.', + ); + } + for (final String keyName in detailedDiagnosticKeys) { + expect( + result.containsKey(keyName), + isTrue, + reason: '$keyName is included.', + ); + } + }); + + test('without full details', () { + final Map result = testTree + .toDiagnosticsNode() + .toJsonMap( + const DiagnosticsSerializationDelegate(), fullDetails: false + ); + expect(result.containsKey('properties'), isFalse); + expect(result.containsKey('children'), isFalse); + + for (final String keyName in essentialDiagnosticKeys) { + expect( + result.containsKey(keyName), + isTrue, + reason: '$keyName is included.', + ); + } + for (final String keyName in detailedDiagnosticKeys) { + expect( + result.containsKey(keyName), + isFalse, + reason: '$keyName is not included.', + ); + } }); test('subtreeDepth 1', () { @@ -279,7 +329,10 @@ class TestDiagnosticsSerializationDelegate implements DiagnosticsSerializationDe final NodeDelegator? nodeDelegator; @override - Map additionalNodeProperties(DiagnosticsNode node) { + Map additionalNodeProperties( + DiagnosticsNode node, { + bool fullDetails = true, + }) { return additionalNodePropertiesMap; } diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index e7c673158c288..e4e9c2fb5c760 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -1982,6 +1982,14 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { return childJson['createdByLocalProject'] == true; } + /// Returns whether the child is missing the "type" field. + /// + /// This should always be true for nodes in the widget tree without + /// full details. + bool isMissingType(Map childJson) { + return childJson['type'] == null; + } + /// Returns whether the child has a description matching [description]. bool hasDescription( Map childJson, { @@ -2278,6 +2286,79 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { }, ))! as Map; + expect( + allChildrenSatisfyCondition(rootJson, + condition: isMissingType, + ), + isFalse, + ); + expect( + allChildrenSatisfyCondition(rootJson, + condition: wasCreatedByLocalProject, + ), + isFalse, + ); + expect( + oneChildSatisfiesCondition(rootJson, condition: (Map child) { + return hasDescription(child, description: 'Text') && + wasCreatedByLocalProject(child) && + !hasTextPreview(child, preview: 'a'); + }, + ), + isTrue, + ); + expect( + oneChildSatisfiesCondition(rootJson, condition: (Map child) { + return hasDescription(child, description: 'Text') && + wasCreatedByLocalProject(child) && + !hasTextPreview(child, preview: 'b'); + }, + ), + isTrue, + ); + expect( + oneChildSatisfiesCondition(rootJson, condition: (Map child) { + return hasDescription(child, description: 'Text') && + wasCreatedByLocalProject(child) && + !hasTextPreview(child, preview: 'c'); + }, + ), + isTrue, + ); + }); + + testWidgets( + 'tree without full details using ext.flutter.inspector.getRootWidgetTree', + (WidgetTester tester) async { + const String group = 'test-group'; + + await pumpWidgetTreeWithABC(tester); + final Element elementA = findElementABC('a'); + final Map jsonA = + await selectedWidgetResponseForElement(elementA); + + final Map creationLocation = + verifyAndReturnCreationLocation(jsonA); + final String testFile = verifyAndReturnTestFile(creationLocation); + addPubRootDirectoryFor(testFile); + + final Map rootJson = (await service.testExtension( + WidgetInspectorServiceExtensions.getRootWidgetTree.name, + { + 'groupName': group, + 'isSummaryTree': 'false', + 'withPreviews': 'false', + 'fullDetails': 'false', + }, + ))! as Map; + + expect( + allChildrenSatisfyCondition(rootJson, + condition: isMissingType, + ), + isTrue, + ); + expect( allChildrenSatisfyCondition(rootJson, condition: wasCreatedByLocalProject, @@ -2337,6 +2418,79 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { }, ))! as Map; + expect( + allChildrenSatisfyCondition(rootJson, + condition: isMissingType, + ), + isFalse, + ); + expect( + allChildrenSatisfyCondition(rootJson, + condition: wasCreatedByLocalProject, + ), + isFalse, + ); + expect( + oneChildSatisfiesCondition(rootJson, condition: (Map child) { + return hasDescription(child, description: 'Text') && + wasCreatedByLocalProject(child) && + hasTextPreview(child, preview: 'a'); + }, + ), + isTrue, + ); + expect( + oneChildSatisfiesCondition(rootJson, condition: (Map child) { + return hasDescription(child, description: 'Text') && + wasCreatedByLocalProject(child) && + hasTextPreview(child, preview: 'b'); + }, + ), + isTrue, + ); + expect( + oneChildSatisfiesCondition(rootJson, condition: (Map child) { + return hasDescription(child, description: 'Text') && + wasCreatedByLocalProject(child) && + hasTextPreview(child, preview: 'c'); + }, + ), + isTrue, + ); + }); + + testWidgets( + 'tree without full details and with previews using ext.flutter.inspector.getRootWidgetTree', + (WidgetTester tester) async { + const String group = 'test-group'; + + await pumpWidgetTreeWithABC(tester); + final Element elementA = findElementABC('a'); + final Map jsonA = + await selectedWidgetResponseForElement(elementA); + + final Map creationLocation = + verifyAndReturnCreationLocation(jsonA); + final String testFile = verifyAndReturnTestFile(creationLocation); + addPubRootDirectoryFor(testFile); + + final Map rootJson = (await service.testExtension( + WidgetInspectorServiceExtensions.getRootWidgetTree.name, + { + 'groupName': group, + 'isSummaryTree': 'false', + 'withPreviews': 'true', + 'fullDetails': 'false', + }, + ))! as Map; + + + expect( + allChildrenSatisfyCondition(rootJson, + condition: isMissingType, + ), + isTrue, + ); expect( allChildrenSatisfyCondition(rootJson, condition: wasCreatedByLocalProject, @@ -5390,6 +5544,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { node.toJsonMap(const DiagnosticsSerializationDelegate()), equals({ 'description': 'description of the deep link', + 'shouldIndent': true, 'type': 'DevToolsDeepLinkProperty', 'name': '', 'style': 'singleLine',