Field Display initial implementation (#4322) Field Display initial implementation for the object inspector tool
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view.dart index 308de4b..963ce01 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics_view.dart
@@ -78,12 +78,12 @@ Flexible( child: Column( children: [ - Flexible( + Expanded( child: GeneralIsolateStatisticsWidget( controller: controller, ), ), - Flexible( + Expanded( child: IsolateMemoryStatisticsWidget( controller: controller, ), @@ -177,34 +177,28 @@ @override Widget build(BuildContext context) { final isolate = controller.isolate; - return Column( - children: [ - Flexible( - child: VMInfoCard( - title: 'Memory', - rowKeyValues: [ - selectableTextBuilderMapEntry( - 'Dart Heap', - _buildMemoryString( - isolate?.dartHeapSize, - isolate?.dartHeapCapacity, - ), - ), - selectableTextBuilderMapEntry( - 'New Space', - _buildMemoryString( - isolate?.newSpaceUsage, - isolate?.newSpaceUsage, - ), - ), - selectableTextBuilderMapEntry( - 'Old Space', - _buildMemoryString( - isolate?.oldSpaceUsage, - isolate?.oldSpaceCapacity, - ), - ), - ], + return VMInfoCard( + title: 'Memory', + rowKeyValues: [ + selectableTextBuilderMapEntry( + 'Dart Heap', + _buildMemoryString( + isolate?.dartHeapSize, + isolate?.dartHeapCapacity, + ), + ), + selectableTextBuilderMapEntry( + 'New Space', + _buildMemoryString( + isolate?.newSpaceUsage, + isolate?.newSpaceUsage, + ), + ), + selectableTextBuilderMapEntry( + 'Old Space', + _buildMemoryString( + isolate?.oldSpaceUsage, + isolate?.oldSpaceCapacity, ), ), ],
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_viewport.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_viewport.dart index f8f2894..e14172a 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/object_viewport.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/object_viewport.dart
@@ -12,6 +12,7 @@ import 'vm_class_display.dart'; import 'vm_code_display.dart'; import 'vm_developer_common_widgets.dart'; +import 'vm_field_display.dart'; import 'vm_object_model.dart'; /// Displays the VM information for the currently selected object in the @@ -81,7 +82,7 @@ return const VMInfoCard(title: 'TO-DO: Display Function object data'); } if (obj is FieldObject) { - return const VMInfoCard(title: 'TO-DO: Display Field object data'); + return VmFieldDisplay(field: obj); } if (obj is LibraryObject) { return const VMInfoCard(title: 'TO-DO: Display Library object data');
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_class_display.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_class_display.dart index e41ff30..9b865ba 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/vm_class_display.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_class_display.dart
@@ -5,10 +5,6 @@ import 'package:flutter/material.dart'; import 'package:vm_service/vm_service.dart'; -import '../../primitives/utils.dart'; -import '../../shared/common_widgets.dart'; -import '../../shared/table.dart'; -import '../../shared/theme.dart'; import 'vm_developer_common_widgets.dart'; import 'vm_object_model.dart'; @@ -30,27 +26,10 @@ Widget build(BuildContext context) { return Row( children: [ - Expanded( - child: Column( - children: [ - ClassInfoWidget( - classDataRows: _classDataRows(clazz), - ), - Flexible( - child: ListView( - children: [ - RetainingPathWidget( - retainingPath: clazz.retainingPath, - onExpanded: _onExpandRetainingPath, - ), - InboundReferencesWidget( - inboundReferences: clazz.inboundReferences, - onExpanded: _onExpandInboundRefs, - ), - ], - ), - ), - ], + Flexible( + child: VmObjectDisplayBasicLayout( + object: clazz, + generalDataRows: _classDataRows(clazz), ), ), if (displayClassInstances) @@ -62,91 +41,17 @@ ], ); } - - void _onExpandRetainingPath(bool expanded) { - if (clazz.retainingPath.value == null) clazz.requestRetainingPath(); - } - - void _onExpandInboundRefs(bool expanded) { - if (clazz.inboundReferences.value == null) clazz.requestInboundsRefs(); - } } -/// Displays general VM information of the Class Object. -class ClassInfoWidget extends StatelessWidget implements PreferredSizeWidget { - const ClassInfoWidget({ - required this.classDataRows, - }); - - final List<MapEntry<String, Widget Function(BuildContext)>> classDataRows; - - @override - Widget build(BuildContext context) { - return SizedBox.fromSize( - size: preferredSize, - child: VMInfoCard( - title: 'General Information', - rowKeyValues: classDataRows, - ), - ); - } - - @override - Size get preferredSize => Size.fromHeight( - areaPaneHeaderHeight + - classDataRows.length * defaultRowHeight + - defaultSpacing, - ); -} - -List<MapEntry<String, Widget Function(BuildContext)>> _classDataRows( +// TODO(mtaylee): Delete 'Currently allocated instances' row when +// ClassInstancesWidget implementation is completed. +/// Generates a list of key-value pairs (map entries) containing the general +/// information of the class object [clazz]. +List<MapEntry<String, WidgetBuilder>> _classDataRows( ClassObject clazz, ) { return [ - selectableTextBuilderMapEntry('Object Class', clazz.obj.type), - selectableTextBuilderMapEntry( - 'Shallow Size', - prettyPrintBytes( - clazz.obj.size ?? 0, - includeUnit: true, - kbFractionDigits: 1, - maxBytes: 512, - ), - ), - MapEntry( - 'Reachable Size', - (context) => ValueListenableBuilder<bool>( - valueListenable: clazz.fetchingReachableSize, - builder: (context, fetching, _) => fetching - ? const CircularProgressIndicator() - : RequestableSizeWidget( - requestedSize: clazz.reachableSize, - requestFunction: clazz.requestReachableSize, - ), - ), - ), - MapEntry( - 'Retained Size', - (context) => ValueListenableBuilder<bool>( - valueListenable: clazz.fetchingRetainedSize, - builder: (context, fetching, _) => fetching - ? const CircularProgressIndicator() - : RequestableSizeWidget( - requestedSize: clazz.retainedSize, - requestFunction: clazz.requestRetainedSize, - ), - ), - ), - selectableTextBuilderMapEntry( - 'Library', - clazz.obj.library?.name?.isEmpty ?? false - ? clazz.script?.uri - : clazz.obj.library?.name, - ), - selectableTextBuilderMapEntry( - 'Script', - '${fileNameFromUri(clazz.script?.uri) ?? ''}:${clazz.pos?.toString() ?? ''}', - ), + ...vmObjectGeneralDataRows(clazz), selectableTextBuilderMapEntry('Superclass', clazz.obj.superClass?.name), selectableTextBuilderMapEntry('SuperType', clazz.obj.superType?.name), selectableTextBuilderMapEntry(
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart index 5ac49d4..97ebd76 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_common_widgets.dart
@@ -10,6 +10,7 @@ import '../../shared/common_widgets.dart'; import '../../shared/table.dart'; import '../../shared/theme.dart'; +import 'vm_object_model.dart'; import 'vm_service_private_extensions.dart'; /// A convenience widget used to create non-scrollable information cards. @@ -29,16 +30,19 @@ }); final String title; - final List<MapEntry<String, Widget Function(BuildContext)>>? rowKeyValues; + final List<MapEntry<String, WidgetBuilder>>? rowKeyValues; final Widget? table; @override Widget build(BuildContext context) { - return Card( - child: VMInfoList( - title: title, - rowKeyValues: rowKeyValues, - table: table, + return SizedBox.fromSize( + size: preferredSize, + child: Card( + child: VMInfoList( + title: title, + rowKeyValues: rowKeyValues, + table: table, + ), ), ); } @@ -56,7 +60,7 @@ } } -MapEntry<String, Widget Function(BuildContext)> selectableTextBuilderMapEntry( +MapEntry<String, WidgetBuilder> selectableTextBuilderMapEntry( String key, String? value, ) { @@ -77,7 +81,7 @@ }); final String title; - final List<MapEntry<String, Widget Function(BuildContext)>>? rowKeyValues; + final List<MapEntry<String, WidgetBuilder>>? rowKeyValues; final Widget? table; @override @@ -204,7 +208,7 @@ objectRefName = (objectRef.name?.isEmpty ?? false) ? objectRef.uri : objectRef.name; } else if (objectRef is ScriptRef) { - objectRefName = fileNameFromUri(objectRef.uri); + objectRefName = _fileNameFromUri(objectRef.uri); } else if (objectRef is InstanceRef) { objectRefName = objectRef.name ?? 'Instance of ${objectRef.classRef?.name ?? '<Class>'}'; @@ -215,6 +219,31 @@ return objectRefName; } +/// Returns the owner name of a Field or Func object, if [object] is a +/// FuncObject, it returns the complete (qualified) name of the owner. +String? _ownerName(VmObject object) { + assert(object is FieldObject || object is FuncObject); + final owner = (object as dynamic).obj.owner as ObjRef?; + + if (owner == null) { + return object.script?.uri; + } + + if (object is FieldObject) { + if (owner is ClassRef || owner is LibraryRef) { + return _objectName(owner); + } + } else if (object is FuncObject) { + if (owner is LibraryRef) { + return _objectName(owner); + } else { + return qualifiedName(owner); + } + } + + throw Exception('Unexpected owner type: ${owner.type}'); +} + /// Returns the name of a function, qualified with the name of /// its owner added as a prefix, separated by a period. /// @@ -518,8 +547,139 @@ } } -String? fileNameFromUri(String? uri) { +String? _fileNameFromUri(String? uri) { if (uri == null) return null; final splitted = uri.split('/'); return splitted[splitted.length - 1]; } + +/// A widget for the object inspector historyViewport containing the main +/// layout of information widgets related to VM object types. +class VmObjectDisplayBasicLayout extends StatelessWidget { + const VmObjectDisplayBasicLayout({ + required this.object, + required this.generalDataRows, + this.sideCardDataRows, + this.generalInfoTitle = 'General Information', + this.sideCardTitle = 'Object Details', + this.expandableWidgets, + }); + + final VmObject object; + final List<MapEntry<String, WidgetBuilder>> generalDataRows; + final List<MapEntry<String, WidgetBuilder>>? sideCardDataRows; + final String generalInfoTitle; + final String sideCardTitle; + final List<Widget>? expandableWidgets; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible( + child: VMInfoCard( + title: generalInfoTitle, + rowKeyValues: generalDataRows, + ), + ), + if (sideCardDataRows != null) + Flexible( + child: VMInfoCard( + title: sideCardTitle, + rowKeyValues: sideCardDataRows, + ), + ), + ], + ), + ), + Flexible( + child: ListView( + children: [ + RetainingPathWidget( + retainingPath: object.retainingPath, + onExpanded: _onExpandRetainingPath, + ), + InboundReferencesWidget( + inboundReferences: object.inboundReferences, + onExpanded: _onExpandInboundRefs, + ), + ...?expandableWidgets, + ], + ), + ), + ], + ); + } + + void _onExpandRetainingPath(bool expanded) { + if (object.retainingPath.value == null) { + object.requestRetainingPath(); + } + } + + void _onExpandInboundRefs(bool expanded) { + if (object.inboundReferences.value == null) { + object.requestInboundsRefs(); + } + } +} + +List<MapEntry<String, WidgetBuilder>> vmObjectGeneralDataRows( + VmObject object, +) { + return [ + selectableTextBuilderMapEntry('Object Class', object.obj.type), + selectableTextBuilderMapEntry( + 'Shallow Size', + prettyPrintBytes( + object.obj.size ?? 0, + includeUnit: true, + kbFractionDigits: 1, + maxBytes: 512, + ), + ), + MapEntry( + 'Reachable Size', + (context) => ValueListenableBuilder<bool>( + valueListenable: object.fetchingReachableSize, + builder: (context, fetching, _) => fetching + ? const CircularProgressIndicator() + : RequestableSizeWidget( + requestedSize: object.reachableSize, + requestFunction: object.requestReachableSize, + ), + ), + ), + MapEntry( + 'Retained Size', + (context) => ValueListenableBuilder<bool>( + valueListenable: object.fetchingRetainedSize, + builder: (context, fetching, _) => fetching + ? const CircularProgressIndicator() + : RequestableSizeWidget( + requestedSize: object.retainedSize, + requestFunction: object.requestRetainedSize, + ), + ), + ), + if (object is ClassObject) + selectableTextBuilderMapEntry( + 'Library', + _objectName(object.obj.library), + ), + if (object is FieldObject || object is FuncObject) + selectableTextBuilderMapEntry( + 'Owner', + _ownerName(object), + ), + if (object is! ScriptObject && object is! LibraryObject) + selectableTextBuilderMapEntry( + 'Script', + '${_fileNameFromUri(object.script?.uri) ?? ''}:${object.pos?.toString() ?? ''}', + ), + ]; +}
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_field_display.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_field_display.dart new file mode 100644 index 0000000..f161fe5 --- /dev/null +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_field_display.dart
@@ -0,0 +1,77 @@ +// Copyright 2022 The Chromium 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/material.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'vm_developer_common_widgets.dart'; +import 'vm_object_model.dart'; +import 'vm_service_private_extensions.dart'; + +/// A widget for the object inspector historyViewport, displaying information +/// related to field objects in the Dart VM. +class VmFieldDisplay extends StatelessWidget { + const VmFieldDisplay({ + required this.field, + }); + + final FieldObject field; + + @override + Widget build(BuildContext context) => VmObjectDisplayBasicLayout( + object: field, + generalDataRows: _fieldDataRows(field), + ); +} + +/// Generates a list of key-value pairs (map entries) containing the general +/// information of the field object [field]. +List<MapEntry<String, WidgetBuilder>> _fieldDataRows( + FieldObject field, +) { + return [ + ...vmObjectGeneralDataRows(field), + selectableTextBuilderMapEntry( + 'Observed types', + _fieldObservedTypes(field), + ), + if (field.obj.staticValue is InstanceRef) + selectableTextBuilderMapEntry( + 'Static Value', + '${field.obj.staticValue.name ?? field.obj.staticValue.classRef.name}: ' + '${field.obj.staticValue.valueAsString ?? 'Unknown value'}', + ), + ]; +} + +/// Returns the observed types of a field object, including null. +/// +/// The observed types can be a single type (guardClassSingle), various types +/// (guardClassDynamic), or a type that has not been observed yet +/// (guardClassUnknown). +String _fieldObservedTypes(FieldObject field) { + String type; + + final kind = field.guardClassKind; + + switch (kind) { + case GuardClassKind.single: + type = field.guardClass!.name ?? '<Observed Type>'; + break; + case GuardClassKind.dynamic: + type = GuardClassKind.dynamic.jsonValue(); + break; + case GuardClassKind.unknown: + type = 'none'; + break; + default: + type = 'Observed types not found'; + } + + final nullable = field.guardNullable == null + ? '' + : ' - null ${field.guardNullable! ? '' : 'not '}observed'; + + return '$type$nullable'; +}
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_object_model.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_object_model.dart index 12568e3..9b4beba 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/vm_object_model.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_object_model.dart
@@ -9,6 +9,7 @@ import '../../shared/globals.dart'; import '../debugger/debugger_model.dart'; import '../debugger/program_explorer_model.dart'; +import 'vm_service_private_extensions.dart'; /// Wrapper class for storing Dart VM objects with their relevant VM /// information. @@ -109,7 +110,8 @@ } } -//TODO(mtaylee): finish class implementation. +/// Stores a 'Class' VM object and provides an interface for obtaining the +/// Dart VM information related to this object. class ClassObject extends VmObject { ClassObject({required super.ref, super.scriptRef, super.outlineNode}); @@ -146,7 +148,8 @@ SourceLocation? get _sourceLocation => obj.location; } -//TODO(mtaylee): finish class implementation. +/// Stores a 'Field' VM object and provides an interface for obtaining the +/// Dart VM information related to this object. class FieldObject extends VmObject { FieldObject({required super.ref, super.scriptRef, super.outlineNode}); @@ -158,6 +161,26 @@ @override SourceLocation? get _sourceLocation => obj.location; + + late final bool? guardNullable; + + late final Class? guardClass; + + late final GuardClassKind? guardClassKind; + + @override + Future<void> initialize() async { + await super.initialize(); + + guardNullable = obj.guardNullable; + guardClassKind = obj.guardClassKind(); + + if (guardClassKind == GuardClassKind.single) { + guardClass = await obj.guardClass; + } else { + guardClass = null; + } + } } //TODO(mtaylee): finish class implementation.
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart index d893d56..835101b 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_service_private_extensions.dart
@@ -4,6 +4,8 @@ import 'package:vm_service/vm_service.dart'; +import '../../shared/globals.dart'; + /// NOTE: this file contains extensions to classes provided by /// `package:vm_service` in order to expose VM internal fields in a controlled /// fashion. Objects and extensions in this class should not be used in @@ -172,7 +174,6 @@ } /// An extension on [Func] which allows for access to VM internal fields. - extension FunctionPrivateViewExtension on Func { static const _unoptimizedCodeKey = '_unoptimizedCode'; @@ -182,7 +183,6 @@ } /// An extension on [Code] which allows for access to VM internal fields. - extension CodePrivateViewExtension on Code { static const _disassemblyKey = '_disassembly'; @@ -192,3 +192,67 @@ set disassembly(Disassembly disassembly) => json![_disassemblyKey] = disassembly.toJson(); } + +/// An extension on [Field] which allows for access to VM internal fields. +extension FieldPrivateViewExtension on Field { + static const guardClassKey = '_guardClass'; + + bool? get guardNullable => json!['_guardNullable']; + + Future<Class?> get guardClass async { + if (_guardClassIsClass()) { + final service = serviceManager.service!; + final isolate = serviceManager.isolateManager.selectedIsolate.value; + + return await service.getObject(isolate!.id!, json![guardClassKey]['id']) + as Class; + } + + return null; + } + + GuardClassKind? guardClassKind() { + if (_guardClassIsClass()) { + return GuardClassKind.single; + } else if (json![guardClassKey] == GuardClassKind.dynamic.jsonValue()) { + return GuardClassKind.dynamic; + } else if (json![guardClassKey] == GuardClassKind.unknown.jsonValue()) { + return GuardClassKind.unknown; + } + + return null; + } + + bool _guardClassIsClass() { + String? guardClassType; + + if (json![guardClassKey] is Map) { + guardClassType = json![guardClassKey]['type']; + } + + if (guardClassType == '@Class' || guardClassType == 'Class') { + return true; + } else { + return false; + } + } +} + +/// The kinds of Guard Class that determine whether a Field object has +/// a unique observed type [single], various observed types [dynamic], +/// or if the field type has not been observed yet [unknown]. +enum GuardClassKind { + single, + dynamic, + unknown; + + String jsonValue() { + switch (this) { + case GuardClassKind.dynamic: + return 'various'; + case GuardClassKind.single: + case GuardClassKind.unknown: + return toString().split('.').last; + } + } +}
diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_statistics_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_statistics_view.dart index 11abc90..5f8655b 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/vm_statistics_view.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_statistics_view.dart
@@ -70,12 +70,12 @@ flex: 3, child: Column( children: [ - Flexible( + Expanded( child: GeneralVMStatisticsWidget( controller: controller, ), ), - Flexible( + Expanded( child: ProcessStatisticsWidget( controller: controller, ),
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/object_inspector_view_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/object_inspector_view_test.dart index 22a65c4..1422771 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/object_inspector_view_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/object_inspector_view_test.dart
@@ -2,10 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/config_specific/ide_theme/ide_theme.dart'; import 'package:devtools_app/src/screens/debugger/program_explorer.dart'; import 'package:devtools_app/src/screens/vm_developer/object_inspector_view.dart'; import 'package:devtools_app/src/screens/vm_developer/object_viewport.dart'; +import 'package:devtools_app/src/scripts/script_manager.dart'; +import 'package:devtools_app/src/service/service_manager.dart'; +import 'package:devtools_app/src/shared/globals.dart'; +import 'package:devtools_app/src/shared/split.dart'; import 'package:devtools_test/devtools_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart';
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/object_viewport_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/object_viewport_test.dart index d6adace..732f151 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/object_viewport_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/object_viewport_test.dart
@@ -2,10 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/config_specific/ide_theme/ide_theme.dart'; import 'package:devtools_app/src/screens/vm_developer/object_viewport.dart'; import 'package:devtools_app/src/screens/vm_developer/vm_class_display.dart'; import 'package:devtools_app/src/screens/vm_developer/vm_developer_common_widgets.dart'; +import 'package:devtools_app/src/screens/vm_developer/vm_field_display.dart'; +import 'package:devtools_app/src/screens/vm_developer/vm_object_model.dart'; +import 'package:devtools_app/src/scripts/script_manager.dart'; +import 'package:devtools_app/src/service/service_manager.dart'; +import 'package:devtools_app/src/shared/globals.dart'; import 'package:devtools_app/src/shared/history_viewport.dart'; import 'package:devtools_test/devtools_test.dart'; import 'package:flutter/material.dart'; @@ -19,6 +24,8 @@ late MockClassObject mockClassObject; + late MockFieldObject mockFieldObject; + late FakeServiceManager fakeServiceManager; late MockScriptManager scriptManager; @@ -53,12 +60,33 @@ when(mockClassObject.fetchingRetainedSize) .thenReturn(ValueNotifier<bool>(false)); when(mockClassObject.retainedSize).thenReturn(null); - when(mockClassObject.retainingPath).thenReturn( - ValueNotifier<RetainingPath?>(null), - ); - when(mockClassObject.inboundReferences).thenReturn( - ValueNotifier<InboundReferences?>(null), - ); + when(mockClassObject.retainingPath) + .thenReturn(ValueNotifier<RetainingPath?>(null)); + when(mockClassObject.inboundReferences) + .thenReturn(ValueNotifier<InboundReferences?>(null)); + + mockFieldObject = MockFieldObject(); + + when(mockFieldObject.outlineNode).thenReturn(null); + when(mockFieldObject.scriptRef).thenReturn(null); + when(mockFieldObject.name).thenReturn(testField.name); + when(mockFieldObject.ref).thenReturn(testField); + when(mockFieldObject.obj).thenReturn(testField); + when(mockFieldObject.script).thenReturn(null); + when(mockFieldObject.pos).thenReturn(null); + when(mockFieldObject.guardClass).thenReturn(null); + when(mockFieldObject.guardNullable).thenReturn(null); + when(mockFieldObject.guardClassKind).thenReturn(null); + when(mockFieldObject.fetchingReachableSize) + .thenReturn(ValueNotifier<bool>(false)); + when(mockFieldObject.reachableSize).thenReturn(null); + when(mockFieldObject.fetchingRetainedSize) + .thenReturn(ValueNotifier<bool>(false)); + when(mockFieldObject.retainedSize).thenReturn(null); + when(mockFieldObject.retainingPath) + .thenReturn(ValueNotifier<RetainingPath?>(null)); + when(mockFieldObject.inboundReferences) + .thenReturn(ValueNotifier<InboundReferences?>(null)); }); testWidgets('builds object viewport', (WidgetTester tester) async { @@ -99,16 +127,16 @@ }); testWidgets('test for Field Object', (WidgetTester tester) async { - final testFieldObject = - TestFieldObject(ref: testField, testField: testField); testObjectInspectorViewController.fakeObjectHistory - .setCurrentObject(testFieldObject); + .setCurrentObject(mockFieldObject); + await tester.pumpWidget( wrap(ObjectViewport(controller: testObjectInspectorViewController)), ); - expect(viewportTitle(testFieldObject), 'Field FooField'); - expect(find.text('Field FooField'), findsOneWidget); - expect(find.byType(VMInfoCard), findsOneWidget); + + expect(viewportTitle(mockFieldObject), 'Field fooField'); + expect(find.text('Field fooField'), findsOneWidget); + expect(find.byType(VmFieldDisplay), findsOneWidget); }); testWidgets('test for Library Object', (WidgetTester tester) async {
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_class_display_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_class_display_test.dart index e369692..bc65f58 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/vm_class_display_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_class_display_test.dart
@@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/config_specific/ide_theme/ide_theme.dart'; import 'package:devtools_app/src/screens/vm_developer/vm_class_display.dart'; import 'package:devtools_app/src/screens/vm_developer/vm_developer_common_widgets.dart'; +import 'package:devtools_app/src/shared/globals.dart'; import 'package:devtools_test/devtools_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -56,7 +57,8 @@ (WidgetTester tester) async { await tester.pumpWidget(wrap(VmClassDisplay(clazz: mockClassObject))); - expect(find.byType(ClassInfoWidget), findsOneWidget); + expect(find.byType(VmObjectDisplayBasicLayout), findsOneWidget); + expect(find.byType(VMInfoCard), findsOneWidget); expect(find.text('General Information'), findsOneWidget); expect(find.text('1 KB'), findsOneWidget); expect(find.text('fooLib'), findsOneWidget); @@ -73,6 +75,5 @@ expect(find.byType(InboundReferencesWidget), findsOneWidget); // TODO(mtaylee): test ClassInstancesWidget when implemented - // expect(find.byType(ClassInstancesWidget), findsOneWidget); }); }
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart index f283e30..917dfe5 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_code_display_test.dart
@@ -4,9 +4,10 @@ import 'dart:math'; -import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/config_specific/ide_theme/ide_theme.dart'; import 'package:devtools_app/src/screens/vm_developer/vm_code_display.dart'; import 'package:devtools_app/src/screens/vm_developer/vm_service_private_extensions.dart'; +import 'package:devtools_app/src/shared/globals.dart'; import 'package:devtools_app/src/shared/table.dart'; import 'package:devtools_test/devtools_test.dart'; import 'package:flutter/material.dart';
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_developer_common_widgets_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_developer_common_widgets_test.dart index ff6c336..e6ea562 100644 --- a/packages/devtools_app/test/vm_developer/object_inspector/vm_developer_common_widgets_test.dart +++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_developer_common_widgets_test.dart
@@ -148,7 +148,7 @@ await tester.pumpAndSettle(); expect(find.byType(SelectableText), findsNWidgets(5)); - expect(find.text('fooClass'), findsOneWidget); + expect(find.text('FooClass'), findsOneWidget); expect( find.text('Retained by element [1] of <parentListName>'), findsOneWidget,
diff --git a/packages/devtools_app/test/vm_developer/object_inspector/vm_field_display_test.dart b/packages/devtools_app/test/vm_developer/object_inspector/vm_field_display_test.dart new file mode 100644 index 0000000..5143e31 --- /dev/null +++ b/packages/devtools_app/test/vm_developer/object_inspector/vm_field_display_test.dart
@@ -0,0 +1,138 @@ +// Copyright 2022 The Chromium 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:devtools_app/src/config_specific/ide_theme/ide_theme.dart'; +import 'package:devtools_app/src/screens/vm_developer/vm_developer_common_widgets.dart'; +import 'package:devtools_app/src/screens/vm_developer/vm_field_display.dart'; +import 'package:devtools_app/src/screens/vm_developer/vm_service_private_extensions.dart'; +import 'package:devtools_app/src/shared/globals.dart'; +import 'package:devtools_test/devtools_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:vm_service/vm_service.dart'; + +import '../vm_developer_test_utils.dart'; + +void main() { + late MockFieldObject mockFieldObject; + + const windowSize = Size(4000.0, 4000.0); + + late Field testFieldCopy; + + late InstanceRef fieldStaticValue; + + setUp(() { + setGlobal(IdeTheme, IdeTheme()); + + mockFieldObject = MockFieldObject(); + + final fieldJson = testField.toJson(); + testFieldCopy = Field.parse(fieldJson)!; + + final instanceJson = testInstance.toJson(); + fieldStaticValue = Instance.parse(instanceJson)!; + + fieldStaticValue.name = 'FooNumberType'; + fieldStaticValue.valueAsString = '100'; + + testFieldCopy.size = 256; + testFieldCopy.staticValue = fieldStaticValue; + + when(mockFieldObject.outlineNode).thenReturn(null); + when(mockFieldObject.scriptRef).thenReturn(null); + when(mockFieldObject.name).thenReturn(testFieldCopy.name); + when(mockFieldObject.ref).thenReturn(testFieldCopy); + when(mockFieldObject.obj).thenReturn(testFieldCopy); + when(mockFieldObject.script).thenReturn(testScript); + when(mockFieldObject.pos).thenReturn(testPos); + + when(mockFieldObject.guardClass).thenReturn(null); + when(mockFieldObject.guardNullable).thenReturn(null); + when(mockFieldObject.guardClassKind).thenReturn(null); + + when(mockFieldObject.fetchingReachableSize) + .thenReturn(ValueNotifier<bool>(false)); + when(mockFieldObject.reachableSize).thenReturn(testRequestableSize); + when(mockFieldObject.fetchingRetainedSize) + .thenReturn(ValueNotifier<bool>(false)); + when(mockFieldObject.retainedSize).thenReturn(testRequestableSize); + when(mockFieldObject.retainingPath).thenReturn( + ValueNotifier<RetainingPath?>(testRetainingPath), + ); + when(mockFieldObject.inboundReferences).thenReturn( + ValueNotifier<InboundReferences?>(testInboundRefs), + ); + }); + + group('field data display tests', () { + testWidgetsWithWindowSize('basic layout', windowSize, + (WidgetTester tester) async { + await tester.pumpWidget(wrap(VmFieldDisplay(field: mockFieldObject))); + + expect(find.byType(VmObjectDisplayBasicLayout), findsOneWidget); + expect(find.byType(VMInfoCard), findsOneWidget); + expect(find.text('General Information'), findsOneWidget); + expect(find.text('Field'), findsOneWidget); + expect(find.text('256 B'), findsOneWidget); + expect(find.text('Owner:'), findsOneWidget); + expect(find.text('fooLib'), findsOneWidget); + expect(find.text('fooScript.dart:10:4'), findsOneWidget); + expect(find.text('Observed types not found'), findsOneWidget); + expect(find.text('Static Value:'), findsOneWidget); + expect(find.text('FooNumberType: 100'), findsOneWidget); + + expect(find.byType(RequestableSizeWidget), findsNWidgets(2)); + + expect(find.byType(RetainingPathWidget), findsOneWidget); + + expect(find.byType(InboundReferencesWidget), findsOneWidget); + }); + + testWidgetsWithWindowSize('observed type single - nullable', windowSize, + (WidgetTester tester) async { + when(mockFieldObject.guardClass).thenReturn(testClass); + when(mockFieldObject.guardNullable).thenReturn(true); + when(mockFieldObject.guardClassKind).thenReturn(GuardClassKind.single); + + await tester.pumpWidget(wrap(VmFieldDisplay(field: mockFieldObject))); + expect(find.text('FooClass - null observed'), findsOneWidget); + }); + + testWidgetsWithWindowSize( + 'observed type dynamic - non-nullable', windowSize, + (WidgetTester tester) async { + when(mockFieldObject.guardClass).thenReturn(null); + when(mockFieldObject.guardNullable).thenReturn(false); + when(mockFieldObject.guardClassKind).thenReturn(GuardClassKind.dynamic); + + await tester.pumpWidget(wrap(VmFieldDisplay(field: mockFieldObject))); + expect(find.text('various - null not observed'), findsOneWidget); + }); + + testWidgetsWithWindowSize( + 'observed type unknown - null unknown', windowSize, + (WidgetTester tester) async { + when(mockFieldObject.guardClass).thenReturn(null); + when(mockFieldObject.guardNullable).thenReturn(null); + when(mockFieldObject.guardClassKind).thenReturn(GuardClassKind.unknown); + + await tester.pumpWidget(wrap(VmFieldDisplay(field: mockFieldObject))); + expect( + find.text('none'), + findsOneWidget, + ); + }); + + testWidgetsWithWindowSize('static value is not InstanceRef', windowSize, + (WidgetTester tester) async { + testFieldCopy.staticValue = testClass; + + await tester.pumpWidget(wrap(VmFieldDisplay(field: mockFieldObject))); + + expect(find.text('Static Value:'), findsNothing); + }); + }); +}
diff --git a/packages/devtools_app/test/vm_developer/vm_developer_test_utils.dart b/packages/devtools_app/test/vm_developer/vm_developer_test_utils.dart index 1259f25..d3c6805 100644 --- a/packages/devtools_app/test/vm_developer/vm_developer_test_utils.dart +++ b/packages/devtools_app/test/vm_developer/vm_developer_test_utils.dart
@@ -19,7 +19,7 @@ ); final testClass = Class( - name: 'fooClass', + name: 'FooClass', library: testLib, isAbstract: false, isConst: false,
diff --git a/packages/devtools_test/lib/src/mocks/generated.dart b/packages/devtools_test/lib/src/mocks/generated.dart index f79631b..511e040 100644 --- a/packages/devtools_test/lib/src/mocks/generated.dart +++ b/packages/devtools_test/lib/src/mocks/generated.dart
@@ -27,6 +27,7 @@ ObjectGroupBase, ClassObject, CodeObject, + FieldObject, ui.Image, ]) void main() {}