Allow requesting a reduced widget tree with getRootWidgetTree service extension (#157309)

This commit is contained in:
Elliott Brooks 2024-10-23 12:30:55 -07:00 committed by GitHub
parent 99ddbc308f
commit 802bae0111
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 380 additions and 45 deletions

View File

@ -1605,12 +1605,29 @@ abstract class DiagnosticsNode {
/// by this method and interactive tree views in the Flutter IntelliJ
/// plugin.
@mustCallSuper
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
Map<String, Object?> result = <String, Object?>{};
assert(() {
final bool hasChildren = getChildren().isNotEmpty;
result = <String, Object?>{
final Map<String, Object?> essentialDetails = <String, Object?>{
'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 : <String, Object?>{
...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<Map<String, Object?>> toJsonList(
List<DiagnosticsNode>? nodes,
DiagnosticsNode? parent,
DiagnosticsSerializationDelegate delegate,
) {
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
bool truncated = false;
if (nodes == null) {
return const <Map<String, Object?>>[];
@ -1674,7 +1686,10 @@ abstract class DiagnosticsNode {
truncated = true;
}
final List<Map<String, Object?>> json = nodes.map<Map<String, Object?>>((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<String> {
final bool quoted;
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
json['quoted'] = quoted;
return json;
}
@ -1913,8 +1937,18 @@ abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> {
}) : super.lazy();
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (unit != null) {
json['unit'] = unit;
}
@ -2097,8 +2131,17 @@ class FlagProperty extends DiagnosticsProperty<bool> {
);
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (ifTrue != null) {
json['ifTrue'] = ifTrue;
}
@ -2219,8 +2262,17 @@ class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
}
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (value != null) {
json['values'] = value!.map<String>((T value) => value.toString()).toList();
}
@ -2357,8 +2409,17 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
}
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (ifPresent != null) {
json['ifPresent'] = ifPresent;
}
@ -2435,8 +2496,17 @@ class FlagsSummary<T> extends DiagnosticsProperty<Map<String, T?>> {
}
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (value.isNotEmpty) {
json['values'] = _formattedValues().toList();
}
@ -2555,7 +2625,10 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
final bool allowNameWrap;
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final T? v = value;
List<Map<String, Object?>>? properties;
if (delegate.expandPropertyValues && delegate.includeProperties && v is Diagnosticable && getProperties().isEmpty) {
@ -2565,9 +2638,16 @@ class DiagnosticsProperty<T> extends DiagnosticsNode {
delegate.filterProperties(v.toDiagnosticsNode().getProperties(), this),
this,
delegate,
fullDetails: fullDetails,
);
}
final Map<String, Object?> json = super.toJsonMap(delegate);
final Map<String, Object?> 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<String, Object?> additionalNodeProperties(DiagnosticsNode node);
Map<String, Object?> 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<String, Object?> additionalNodeProperties(DiagnosticsNode node) {
Map<String, Object?> additionalNodeProperties(
DiagnosticsNode node, {
bool fullDetails = true,
}) {
return const <String, Object?>{};
}

View File

@ -495,8 +495,17 @@ class ColorProperty extends DiagnosticsProperty<Color> {
});
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (value != null) {
json['valueProperties'] = <String, Object>{
'red': value!.red,

View File

@ -5379,13 +5379,18 @@ class _ElementDiagnosticableTreeNode extends DiagnosticableTreeNode {
final bool stateful;
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> 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;
}
}

View File

@ -121,8 +121,17 @@ class IconDataProperty extends DiagnosticsProperty<IconData> {
});
@override
Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
final Map<String, Object?> json = super.toJsonMap(delegate);
Map<String, Object?> toJsonMap(
DiagnosticsSerializationDelegate delegate, {
bool fullDetails = true,
}) {
final Map<String, Object?> json = super.toJsonMap(
delegate,
fullDetails: fullDetails,
);
if (!fullDetails) {
return json;
}
if (value != null) {
json['valueProperties'] = <String, Object>{
'codePoint': value!.codePoint,

View File

@ -1729,9 +1729,11 @@ mixin WidgetInspectorService {
Map<String, Object?>? _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<DiagnosticsNode> 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<String, Object?>? result = _getRootWidgetTreeImpl(
groupName: groupName,
isSummaryTree: isSummaryTree,
withPreviews: withPreviews,
fullDetails: fullDetails,
);
return Future<Map<String, dynamic>>.value(<String, dynamic>{
@ -1992,6 +2003,7 @@ mixin WidgetInspectorService {
required String groupName,
required bool isSummaryTree,
required bool withPreviews,
bool fullDetails = true,
Map<String, Object>? 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<String, Object?> additionalNodeProperties(DiagnosticsNode node) {
Map<String, Object?> additionalNodeProperties(
DiagnosticsNode node, {
bool fullDetails = true,
}) {
final Map<String, Object?> result = <String, Object?>{};
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;

View File

@ -24,6 +24,16 @@ void main() {
});
group('Serialization', () {
const List<String> essentialDiagnosticKeys = <String>[
'description',
'shouldIndent',
];
const List<String> detailedDiagnosticKeys = <String>[
'type',
'hasChildren',
'allowWrap',
];
final TestTree testTree = TestTree(
properties: <DiagnosticsNode>[
StringProperty('stringProperty1', 'value1', quoted: false),
@ -70,6 +80,46 @@ void main() {
final Map<String, Object?> 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<String, Object?> 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<String, Object> additionalNodeProperties(DiagnosticsNode node) {
Map<String, Object> additionalNodeProperties(
DiagnosticsNode node, {
bool fullDetails = true,
}) {
return additionalNodePropertiesMap;
}

View File

@ -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<String, Object?> childJson) {
return childJson['type'] == null;
}
/// Returns whether the child has a description matching [description].
bool hasDescription(
Map<String, Object?> childJson, {
@ -2278,6 +2286,79 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
},
))! as Map<String, Object?>;
expect(
allChildrenSatisfyCondition(rootJson,
condition: isMissingType,
),
isFalse,
);
expect(
allChildrenSatisfyCondition(rootJson,
condition: wasCreatedByLocalProject,
),
isFalse,
);
expect(
oneChildSatisfiesCondition(rootJson, condition: (Map<String, Object?> child) {
return hasDescription(child, description: 'Text') &&
wasCreatedByLocalProject(child) &&
!hasTextPreview(child, preview: 'a');
},
),
isTrue,
);
expect(
oneChildSatisfiesCondition(rootJson, condition: (Map<String, Object?> child) {
return hasDescription(child, description: 'Text') &&
wasCreatedByLocalProject(child) &&
!hasTextPreview(child, preview: 'b');
},
),
isTrue,
);
expect(
oneChildSatisfiesCondition(rootJson, condition: (Map<String, Object?> 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<String, dynamic> jsonA =
await selectedWidgetResponseForElement(elementA);
final Map<String, Object?> creationLocation =
verifyAndReturnCreationLocation(jsonA);
final String testFile = verifyAndReturnTestFile(creationLocation);
addPubRootDirectoryFor(testFile);
final Map<String, Object?> rootJson = (await service.testExtension(
WidgetInspectorServiceExtensions.getRootWidgetTree.name,
<String, String>{
'groupName': group,
'isSummaryTree': 'false',
'withPreviews': 'false',
'fullDetails': 'false',
},
))! as Map<String, Object?>;
expect(
allChildrenSatisfyCondition(rootJson,
condition: isMissingType,
),
isTrue,
);
expect(
allChildrenSatisfyCondition(rootJson,
condition: wasCreatedByLocalProject,
@ -2337,6 +2418,79 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
},
))! as Map<String, Object?>;
expect(
allChildrenSatisfyCondition(rootJson,
condition: isMissingType,
),
isFalse,
);
expect(
allChildrenSatisfyCondition(rootJson,
condition: wasCreatedByLocalProject,
),
isFalse,
);
expect(
oneChildSatisfiesCondition(rootJson, condition: (Map<String, Object?> child) {
return hasDescription(child, description: 'Text') &&
wasCreatedByLocalProject(child) &&
hasTextPreview(child, preview: 'a');
},
),
isTrue,
);
expect(
oneChildSatisfiesCondition(rootJson, condition: (Map<String, Object?> child) {
return hasDescription(child, description: 'Text') &&
wasCreatedByLocalProject(child) &&
hasTextPreview(child, preview: 'b');
},
),
isTrue,
);
expect(
oneChildSatisfiesCondition(rootJson, condition: (Map<String, Object?> 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<String, dynamic> jsonA =
await selectedWidgetResponseForElement(elementA);
final Map<String, Object?> creationLocation =
verifyAndReturnCreationLocation(jsonA);
final String testFile = verifyAndReturnTestFile(creationLocation);
addPubRootDirectoryFor(testFile);
final Map<String, Object?> rootJson = (await service.testExtension(
WidgetInspectorServiceExtensions.getRootWidgetTree.name,
<String, String>{
'groupName': group,
'isSummaryTree': 'false',
'withPreviews': 'true',
'fullDetails': 'false',
},
))! as Map<String, Object?>;
expect(
allChildrenSatisfyCondition(rootJson,
condition: isMissingType,
),
isTrue,
);
expect(
allChildrenSatisfyCondition(rootJson,
condition: wasCreatedByLocalProject,
@ -5390,6 +5544,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
node.toJsonMap(const DiagnosticsSerializationDelegate()),
equals(<String, dynamic>{
'description': 'description of the deep link',
'shouldIndent': true,
'type': 'DevToolsDeepLinkProperty',
'name': '',
'style': 'singleLine',