From 0635092f1c17103f0ef0a5dcaf33bb95bd1503af Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 22 May 2025 12:24:59 -0700 Subject: [PATCH 1/6] Fix copying issue for inspector nodes --- .../shared/diagnostics/dart_object_node.dart | 27 ++++++++++++++++--- .../macos/Runner.xcodeproj/project.pbxproj | 6 ++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart index b7279fb7b9f..1fa9243d6da 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart @@ -372,11 +372,30 @@ class DartObjectNode extends TreeNode { String toString() { if (text != null) return text!; + // If the name is provided, use it followed by the instanceRef. final instanceRef = ref!.instanceRef; - final value = instanceRef is InstanceRef - ? instanceRef.valueAsString - : instanceRef; - return '$name - $value'; + if ((name ?? '').isNotEmpty) { + final value = instanceRef is InstanceRef + ? instanceRef.valueAsString + : instanceRef; + return '$name - $value'; + } + + // Use the diagnostics node (if it exists). This is only provided for + // Inspector nodes. + final diagnostic = ref?.diagnostic; + final description = diagnostic?.description; + if (diagnostic != null && description != null) { + final separator = diagnostic.separator; + final textPreview = diagnostic.json['textPreview']; + return textPreview != null + ? '$description$separator $textPreview' + : description; + } + + // Fallback to returning the instanceRef as a String if none of the above + // cases are true. + return instanceRef.toString(); } /// Selects the object in the Flutter Widget inspector. diff --git a/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj b/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj index e9c8ca8c9dc..59aa8f360ec 100644 --- a/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj @@ -553,7 +553,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -632,7 +632,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -679,7 +679,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; From 0782adf5c42e358cab9e9c8be3026dce2c7cdd60 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 22 May 2025 16:37:57 -0700 Subject: [PATCH 2/6] Add tests --- .../shared/diagnostics/dart_object_node.dart | 17 ++-- .../diagnostics/dart_object_node_test.dart | 37 +++++++++ .../test/test_infra/utils/variable_utils.dart | 79 ++++++++++++++++++- 3 files changed, 124 insertions(+), 9 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart index 1fa9243d6da..8aa7d985549 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart @@ -374,11 +374,13 @@ class DartObjectNode extends TreeNode { // If the name is provided, use it followed by the instanceRef. final instanceRef = ref!.instanceRef; - if ((name ?? '').isNotEmpty) { - final value = instanceRef is InstanceRef - ? instanceRef.valueAsString - : instanceRef; - return '$name - $value'; + if (instanceRef != null && (name ?? '').isNotEmpty) { + final length = instanceRef.length; + if (instanceRef.length != null) { + return '$name - ${instanceRef.kind} ($length)'; + } + + return '$name - ${instanceRef.valueAsString}'; } // Use the diagnostics node (if it exists). This is only provided for @@ -393,9 +395,8 @@ class DartObjectNode extends TreeNode { : description; } - // Fallback to returning the instanceRef as a String if none of the above - // cases are true. - return instanceRef.toString(); + // Fallback to returning the runtime type as a catch-all. + return ref.runtimeType.toString(); } /// Selects the object in the Flutter Widget inspector. diff --git a/packages/devtools_app/test/shared/diagnostics/dart_object_node_test.dart b/packages/devtools_app/test/shared/diagnostics/dart_object_node_test.dart index 675dbc9b6ce..95be5046d8a 100644 --- a/packages/devtools_app/test/shared/diagnostics/dart_object_node_test.dart +++ b/packages/devtools_app/test/shared/diagnostics/dart_object_node_test.dart @@ -105,4 +105,41 @@ void main() { expect(str.childCount, equals(0)); expect(str.isPartialObject, isFalse); }); + + group('toString', () { + test('string variable', () { + final str = buildStringVariable('Hello there!'); + expect(str.toString(), equals('root1 - Hello there!')); + }); + + test('boolean variable', () { + final boolean = buildBooleanVariable(true); + expect(boolean.toString(), equals('root1 - true')); + }); + + test('set variable', () { + final set = buildSetVariable(length: 3); + expect(set.toString(), equals('root1 - Set (3)')); + }); + + test('map variable', () { + final map = buildMapVariable(length: 3); + expect(map.toString(), equals('root1 - Map (3)')); + }); + + test('string variable', () { + final list = buildListVariable(length: 3); + expect(list.toString(), equals('root1 - List (3)')); + }); + + testWidgets('Text widget', (WidgetTester tester) async { + final textWidget = buildTextWidgetVariable(); + expect(textWidget.toString(), equals('Text: Hello world!')); + }); + + testWidgets('Row widget', (WidgetTester tester) async { + final rowWidget = buildRowWidgetVariable(); + expect(rowWidget.toString(), equals('Row')); + }); + }); } diff --git a/packages/devtools_app/test/test_infra/utils/variable_utils.dart b/packages/devtools_app/test/test_infra/utils/variable_utils.dart index 5202a449c00..1515c977ff9 100644 --- a/packages/devtools_app/test/test_infra/utils/variable_utils.dart +++ b/packages/devtools_app/test/test_infra/utils/variable_utils.dart @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. +import 'dart:convert'; + import 'package:devtools_app/src/shared/diagnostics/dart_object_node.dart'; +import 'package:devtools_app/src/shared/diagnostics/diagnostics_node.dart'; import 'package:devtools_app/src/shared/diagnostics/generic_instance_reference.dart'; import 'package:vm_service/vm_service.dart'; @@ -213,6 +216,22 @@ DartObjectNode buildBooleanVariable(bool value) { ); } +DartObjectNode buildTextWidgetVariable() { + return DartObjectNode.fromValue( + value: null, + isolateRef: _isolateRef, + diagnostic: _textWidgetDiagnosticNode, + ); +} + +DartObjectNode buildRowWidgetVariable() { + return DartObjectNode.fromValue( + value: null, + isolateRef: _isolateRef, + diagnostic: _rowWidgetDiagnosticNode, + ); +} + InstanceRef _buildInstanceRefForMap({required int length}) => InstanceRef( id: _incrementRef(), kind: InstanceKind.kMap, @@ -266,5 +285,63 @@ int _rootNumber = 0; String _incrementRoot() { _rootNumber++; - return 'Root $_rootNumber'; + return 'root$_rootNumber'; +} + +final _textWidgetDiagnosticNode = RemoteDiagnosticsNode( + jsonDecode(_textWidgetDiagnosticJson), + null, + false, + null, +); + +final _rowWidgetDiagnosticNode = RemoteDiagnosticsNode( + jsonDecode(_rowWidgetDiagnosticJson), + null, + false, + null, +); + +const _textWidgetDiagnosticJson = ''' +{ + "description": "Text", + "type": "_ElementDiagnosticableTreeNode", + "style": "dense", + "hasChildren": true, + "allowWrap": false, + "summaryTree": true, + "locationId": 0, + "creationLocation": { + "file": "file:///Users/prismo/flutter_app/main.dart", + "line": 109, + "column": 23, + "name": "Text" + }, + "createdByLocalProject": true, + "textPreview": "Hello world!", + "children": [], + "widgetRuntimeType": "Text", + "stateful": false +} +'''; + +const _rowWidgetDiagnosticJson = ''' +{ + "description": "Row", + "type": "_ElementDiagnosticableTreeNode", + "hasChildren": true, + "allowWrap": false, + "summaryTree": true, + "locationId": 0, + "creationLocation": { + "file": "file:///Users/prismo/flutter_app/main.dart", + "line": 109, + "column": 23, + "name": "Row" + }, + "createdByLocalProject": true, + "children": [], + "widgetRuntimeType": "Row", + "stateful": false } +'''; From 35eea563c72eebd36c29a4a20357def989785d2e Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 22 May 2025 16:45:25 -0700 Subject: [PATCH 3/6] Update comments --- .../lib/src/shared/diagnostics/dart_object_node.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart index 8aa7d985549..70afeb226d6 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart @@ -372,14 +372,17 @@ class DartObjectNode extends TreeNode { String toString() { if (text != null) return text!; - // If the name is provided, use it followed by the instanceRef. final instanceRef = ref!.instanceRef; if (instanceRef != null && (name ?? '').isNotEmpty) { final length = instanceRef.length; + // Show the variable name, kind, and length for instance kinds that have a + // length (maps, lists, sets, etc). if (instanceRef.length != null) { return '$name - ${instanceRef.kind} ($length)'; } + // Show the variable name and value for instance kinds without a length + //(e.g. strings, booleans, ints). return '$name - ${instanceRef.valueAsString}'; } From 7bcdb9b6e95f1de82deebf528009a2961d55cdce Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 27 May 2025 11:24:08 -0700 Subject: [PATCH 4/6] Respond to PR comments --- .../lib/src/shared/diagnostics/dart_object_node.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart index 70afeb226d6..f6cc1a7f01f 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/dart_object_node.dart @@ -373,7 +373,7 @@ class DartObjectNode extends TreeNode { if (text != null) return text!; final instanceRef = ref!.instanceRef; - if (instanceRef != null && (name ?? '').isNotEmpty) { + if (instanceRef != null && !name.isNullOrEmpty) { final length = instanceRef.length; // Show the variable name, kind, and length for instance kinds that have a // length (maps, lists, sets, etc). @@ -390,8 +390,8 @@ class DartObjectNode extends TreeNode { // Inspector nodes. final diagnostic = ref?.diagnostic; final description = diagnostic?.description; - if (diagnostic != null && description != null) { - final separator = diagnostic.separator; + if (description != null) { + final separator = diagnostic!.separator; final textPreview = diagnostic.json['textPreview']; return textPreview != null ? '$description$separator $textPreview' From a67886295a9c301fc1d41a6b89f9ba516240a824 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 27 May 2025 13:33:33 -0700 Subject: [PATCH 5/6] Fix test --- .../debugger/debugger_screen_variables_test.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/devtools_app/test/screens/debugger/debugger_screen_variables_test.dart b/packages/devtools_app/test/screens/debugger/debugger_screen_variables_test.dart index 0ee4089e899..31145cf4057 100644 --- a/packages/devtools_app/test/screens/debugger/debugger_screen_variables_test.dart +++ b/packages/devtools_app/test/screens/debugger/debugger_screen_variables_test.dart @@ -114,18 +114,18 @@ void main() { await pumpDebuggerScreen(tester, debuggerController); expect(find.text('Variables'), findsOneWidget); - final listFinder = find.text('Root 1: List (2 items)'); + final listFinder = find.text('root1: List (2 items)'); expect(listFinder, findsOneWidget); - final mapFinder = find.textContaining('Root 2: Map (2 items)'); + final mapFinder = find.textContaining('root2: Map (2 items)'); final mapElement1Finder = find.textContaining("['key1']: 1.0"); final mapElement2Finder = find.textContaining("['key2']: 2.0"); expect(listFinder, findsOneWidget); expect(mapFinder, findsOneWidget); - expect(find.textContaining("Root 3: 'test str...'"), findsOneWidget); - expect(find.textContaining('Root 4: true'), findsOneWidget); + expect(find.textContaining("root3: 'test str...'"), findsOneWidget); + expect(find.textContaining('root4: true'), findsOneWidget); // Initially list is not expanded. expect(find.textContaining('0: 3'), findsNothing); @@ -148,7 +148,7 @@ void main() { expect(mapElement2Finder, findsOneWidget); // Expect a tooltip for the set instance. - final setFinder = find.text('Root 5: Set (2 items)'); + final setFinder = find.text('root5: Set (2 items)'); expect(setFinder, findsOneWidget); // Initially set is not expanded. @@ -174,7 +174,7 @@ void main() { await pumpDebuggerScreen(tester, debuggerController); - final listFinder = find.text('Root 1: List (243,621 items)'); + final listFinder = find.text('root1: List (243,621 items)'); await verifyGroupings(tester, parentFinder: listFinder); }, ); @@ -191,7 +191,7 @@ void main() { await pumpDebuggerScreen(tester, debuggerController); - final mapFinder = find.text('Root 1: Map (243,621 items)'); + final mapFinder = find.text('root1: Map (243,621 items)'); await verifyGroupings(tester, parentFinder: mapFinder); }, ); @@ -208,7 +208,7 @@ void main() { await pumpDebuggerScreen(tester, debuggerController); - final setFinder = find.text('Root 1: Set (243,621 items)'); + final setFinder = find.text('root1: Set (243,621 items)'); await verifyGroupings(tester, parentFinder: setFinder); }, ); From 6291f726cbf3f6957130ce0130eef6eb83cc87aa Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 27 May 2025 13:40:08 -0700 Subject: [PATCH 6/6] Update release notes --- packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 915e386117f..75a8bfc0cfc 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -19,6 +19,7 @@ To learn more about DevTools, check out the DevTools. - [#9125](https://github.com/flutter/devtools/pull/9125) - Dismiss stale banner messages when the connected app state changes. - [#9148](https://github.com/flutter/devtools/pull/9148) - Fix a focus traversal issue with search fields. [#9166](https://github.com/flutter/devtools/pull/9166) +- Fix an issue where copying all logs in the console would show `null` for any inspected widgets. - [#9204](https://github.com/flutter/devtools/pull/9204) ## Inspector updates