From 7214cb28be0dfaa1df3893ca0a7de3b73b3732e3 Mon Sep 17 00:00:00 2001 From: Polina Cherkasova Date: Fri, 16 Jun 2023 10:45:34 -0700 Subject: [PATCH] Accept Diagnosticable as input in inspector API. (#128962) --- .../lib/src/widgets/widget_inspector.dart | 30 ++++---- .../test/widgets/widget_inspector_test.dart | 68 ++++++++++++++++++- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index cefc1e8e89..027d3ef61a 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -1762,7 +1762,7 @@ mixin WidgetInspectorService { DiagnosticsNode? _idToDiagnosticsNode(String? diagnosticsOrDiagnosticableId) { // TODO(polina-c): start always assuming Diagnosticable, when DevTools stops sending DiagnosticsNode to - // getChildrenSummaryTree and getProperties. + // APIs that invoke this method. // https://github.com/flutter/devtools/issues/3951 final Object? theObject = toObject(diagnosticsOrDiagnosticableId); if (theObject is DiagnosticsNode) { @@ -1788,17 +1788,17 @@ mixin WidgetInspectorService { } /// Returns a JSON representation of the children of the [DiagnosticsNode] - /// object that `diagnosticsNodeId` references providing information needed + /// object that [diagnosticsOrDiagnosticableId] references providing information needed /// for the details subtree view. /// /// The details subtree shows properties inline and includes all children /// rather than a filtered set of important children. - String getChildrenDetailsSubtree(String diagnosticsNodeId, String groupName) { - return _safeJsonEncode(_getChildrenDetailsSubtree(diagnosticsNodeId, groupName)); + String getChildrenDetailsSubtree(String diagnosticsOrDiagnosticableId, String groupName) { + return _safeJsonEncode(_getChildrenDetailsSubtree(diagnosticsOrDiagnosticableId, groupName)); } - List _getChildrenDetailsSubtree(String? diagnosticsNodeId, String groupName) { - final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?; + List _getChildrenDetailsSubtree(String? diagnosticsOrDiagnosticableId, String groupName) { + final DiagnosticsNode? node = _idToDiagnosticsNode(diagnosticsOrDiagnosticableId); // With this value of minDepth we only expand one extra level of important nodes. final InspectorSerializationDelegate delegate = InspectorSerializationDelegate(groupName: groupName, includeProperties: true, service: this); return _nodesToJson(node == null ? const [] : _getChildrenFiltered(node, delegate), delegate, parent: node); @@ -1910,19 +1910,19 @@ mixin WidgetInspectorService { /// * [getChildrenDetailsSubtree], a method to get children of a node /// in the details subtree. String getDetailsSubtree( - String id, + String diagnosticsOrDiagnosticableId, String groupName, { int subtreeDepth = 2, }) { - return _safeJsonEncode(_getDetailsSubtree( id, groupName, subtreeDepth)); + return _safeJsonEncode(_getDetailsSubtree(diagnosticsOrDiagnosticableId, groupName, subtreeDepth)); } Map? _getDetailsSubtree( - String? id, + String? diagnosticsOrDiagnosticableId, String? groupName, int subtreeDepth, ) { - final DiagnosticsNode? root = toObject(id) as DiagnosticsNode?; + final DiagnosticsNode? root = _idToDiagnosticsNode(diagnosticsOrDiagnosticableId); if (root == null) { return null; } @@ -1942,6 +1942,8 @@ mixin WidgetInspectorService { /// If the currently selected [Element] is identical to the [Element] /// referenced by `previousSelectionId` then the previous [DiagnosticsNode] is /// reused. + // TODO(polina-c): delete [previousSelectionId] when it is not used in DevTools + // https://github.com/flutter/devtools/issues/3951 @protected String getSelectedWidget(String? previousSelectionId, String groupName) { return _safeJsonEncode(_getSelectedWidget(previousSelectionId, groupName)); @@ -2020,18 +2022,18 @@ mixin WidgetInspectorService { Future> _getLayoutExplorerNode( Map parameters, ) { - final String? id = parameters['id']; + final String? diagnosticsOrDiagnosticableId = parameters['id']; final int subtreeDepth = int.parse(parameters['subtreeDepth']!); final String? groupName = parameters['groupName']; Map? result = {}; - final Object? root = toObject(id); + final DiagnosticsNode? root = _idToDiagnosticsNode(diagnosticsOrDiagnosticableId); if (root == null) { return Future>.value({ 'result': result, }); } result = _nodeToJson( - root as DiagnosticsNode, + root, InspectorSerializationDelegate( groupName: groupName, summaryTree: true, @@ -2232,6 +2234,8 @@ mixin WidgetInspectorService { /// If the currently selected [Element] is identical to the [Element] /// referenced by `previousSelectionId` then the previous [DiagnosticsNode] is /// reused. + // TODO(polina-c): delete paramater [previousSelectionId] when it is not used in DevTools + // https://github.com/flutter/devtools/issues/3951 String getSelectedSummaryWidget(String previousSelectionId, String groupName) { return _safeJsonEncode(_getSelectedSummaryWidget(previousSelectionId, groupName)); } diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index a79f1ed4fa..d0ae934d33 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -900,6 +900,8 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { }); test('WidgetInspectorService getProperties for $DiagnosticsNode', () { + // TODO(polina-c): delete this test once getChildrenDetailsSubtree stops accepting DiagnosticsNode. + // https://github.com/flutter/devtools/issues/3951 final DiagnosticsNode diagnostic = const Text('a', textDirection: TextDirection.ltr).toDiagnosticsNode(); const String group = 'group'; service.disposeAllGroups(); @@ -2124,6 +2126,8 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { }); test('ext.flutter.inspector.getProperties for $DiagnosticsNode', () async { + // TODO(polina-c): delete this test once getChildrenDetailsSubtree stops accepting DiagnosticsNode. + // https://github.com/flutter/devtools/issues/3951 final DiagnosticsNode diagnostic = const Text('a', textDirection: TextDirection.ltr).toDiagnosticsNode(); const String group = 'group'; final String id = service.toId(diagnostic, group)!; @@ -5090,7 +5094,9 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(box2.localToGlobal(Offset.zero), equals(position2)); }); - testWidgets('getChildrenDetailsSubtree', (WidgetTester tester) async { + testWidgets('getChildrenDetailsSubtree with $DiagnosticsNode', (WidgetTester tester) async { + // TODO(polina-c): delete this test once getChildrenDetailsSubtree stops accepting DiagnosticsNode. + // https://github.com/flutter/devtools/issues/3951 await tester.pumpWidget( MaterialApp( title: 'Hello, World', @@ -5150,6 +5156,66 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(appBars.single, isNot(contains('children'))); }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag. + testWidgets('getChildrenDetailsSubtree with $Diagnosticable', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + title: 'Hello, World', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: Scaffold( + appBar: AppBar( + title: const Text('Hello, World'), + ), + body: const Center( + child: Text('Hello, World!'), + ), + ), + ), + ); + service.setSelection(find.text('Hello, World!').evaluate().first, 'my-group'); + + // Figure out the pubRootDirectory + final Map jsonObject = (await service.testExtension( + WidgetInspectorServiceExtensions.getSelectedWidget.name, + {'objectGroup': 'my-group'}, + ))! as Map; + final Map creationLocation = jsonObject['creationLocation']! as Map; + expect(creationLocation, isNotNull); + final String file = creationLocation['file']! as String; + expect(file, endsWith('widget_inspector_test.dart')); + final List segments = Uri.parse(file).pathSegments; + // Strip a couple subdirectories away to generate a plausible pub rootdirectory. + final String pubRootTest = '/${segments.take(segments.length - 2).join('/')}'; + service.resetPubRootDirectories(); + service.addPubRootDirectories([pubRootTest]); + + final String summary = service.getRootWidgetSummaryTree('foo1'); + // ignore: avoid_dynamic_calls + final List childrenOfRoot = json.decode(summary)['children'] as List; + final List childrenOfMaterialApp = (childrenOfRoot.first! as Map)['children']! as List; + final Map scaffold = childrenOfMaterialApp.first! as Map; + expect(scaffold['description'], 'Scaffold'); + final String objectId = scaffold['valueId']! as String; + final String details = service.getDetailsSubtree(objectId, 'foo2'); + // ignore: avoid_dynamic_calls + final List detailedChildren = json.decode(details)['children'] as List; + + final List> appBars = >[]; + void visitChildren(List children) { + for (final Map child in children.cast>()) { + if (child['description'] == 'AppBar') { + appBars.add(child); + } + if (child.containsKey('children')) { + visitChildren(child['children']! as List); + } + } + } + visitChildren(detailedChildren); + expect(appBars.single, isNot(contains('children'))); + }, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag. + testWidgets('InspectorSerializationDelegate addAdditionalPropertiesCallback', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp(