diff --git a/packages/devtools_app/lib/src/shared/editor/api_classes.dart b/packages/devtools_app/lib/src/shared/editor/api_classes.dart index 1fa1ff8160c..5487a440c1e 100644 --- a/packages/devtools_app/lib/src/shared/editor/api_classes.dart +++ b/packages/devtools_app/lib/src/shared/editor/api_classes.dart @@ -110,6 +110,7 @@ abstract class Field { static const hasArgument = 'hasArgument'; static const id = 'id'; static const isDarkMode = 'isDarkMode'; + static const isDeprecated = 'isDeprecated'; static const isEditable = 'isEditable'; static const isNullable = 'isNullable'; static const isRequired = 'isRequired'; @@ -524,6 +525,7 @@ class EditableArgument with Serializable { required this.isNullable, required this.isRequired, required this.isEditable, + required this.isDeprecated, required this.hasDefault, this.options, this.value, @@ -540,6 +542,7 @@ class EditableArgument with Serializable { isNullable: (map[Field.isNullable] as bool?) ?? false, isRequired: (map[Field.isRequired] as bool?) ?? false, isEditable: (map[Field.isEditable] as bool?) ?? true, + isDeprecated: (map[Field.isDeprecated] as bool?) ?? false, hasDefault: map.containsKey(Field.defaultValue), options: (map[Field.options] as List?)?.cast(), value: map[Field.value], @@ -575,6 +578,9 @@ class EditableArgument with Serializable { /// where previous positional parameters have no argument. final bool isEditable; + /// Whether the argument is deprecated. + final bool isDeprecated; + /// A list of values that could be provided for this argument. /// /// This will only be included if the parameter's [type] is "enum". @@ -613,6 +619,7 @@ class EditableArgument with Serializable { Field.isNullable: isNullable, Field.isRequired: isRequired, Field.isEditable: isEditable, + Field.isDeprecated: isDeprecated, Field.options: options, Field.displayValue: displayValue, Field.errorText: errorText, diff --git a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart index 2ec0be704e5..c671236127d 100644 --- a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart +++ b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart @@ -156,6 +156,8 @@ class PropertyEditorController extends DisposableController (result?.args ?? []) .map(argToProperty) .nonNulls + // Filter out any deprecated properties that aren't set. + .where((property) => !property.isDeprecated || property.hasArgument) .toList(); final name = result?.name; _editableWidgetData.value = ( diff --git a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_types.dart b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_types.dart index 39b3a634703..56c2e43f1cf 100644 --- a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_types.dart +++ b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_types.dart @@ -143,6 +143,7 @@ class EditableProperty extends EditableArgument { isNullable: argument.isNullable, isRequired: argument.isRequired, isEditable: argument.isEditable, + isDeprecated: argument.isDeprecated, options: argument.options, displayValue: argument.displayValue, errorText: argument.errorText, diff --git a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_view.dart b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_view.dart index b1d11ec837e..03b0a3b0b7f 100644 --- a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_view.dart +++ b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_view.dart @@ -195,6 +195,7 @@ class _PropertyLabels extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isSet = property.hasArgument; + final isDeprecated = property.isDeprecated; final isDefault = property.isDefault; return LayoutBuilder( @@ -214,7 +215,22 @@ class _PropertyLabels extends StatelessWidget { textColor: colorScheme.onPrimary, ), ), - if (isDefault) + // We exclude deprecated properties that are not set, so this label + // is always displayed under the "set" label. + if (isDeprecated) + Padding( + padding: _labelPadding(isTopLabel: !isSet), + child: RoundedLabel( + labelText: _maybeTruncateLabel('deprecated', width: width), + tooltipText: 'Property argument is deprecated.', + fontSize: smallFontSize, + backgroundColor: colorScheme.error, + textColor: colorScheme.onError, + ), + ), + // We only have space for two labels, so the deprecated label takes + // precedence over the default label. + if (isDefault && !isDeprecated) Padding( padding: _labelPadding(isTopLabel: !isSet), child: RoundedLabel( diff --git a/packages/devtools_app/test/standalone_ui/ide_shared/property_editor/property_editor_test.dart b/packages/devtools_app/test/standalone_ui/ide_shared/property_editor/property_editor_test.dart index fe6b7cc597a..28c88d4dacf 100644 --- a/packages/devtools_app/test/standalone_ui/ide_shared/property_editor/property_editor_test.dart +++ b/packages/devtools_app/test/standalone_ui/ide_shared/property_editor/property_editor_test.dart @@ -30,6 +30,8 @@ void main() { (document: textDocument2, position: activeCursorPosition2): result2, (document: textDocument1, position: activeCursorPosition3): resultWithText, (document: textDocument1, position: activeCursorPosition4): resultWithTitle, + (document: textDocument1, position: activeCursorPosition5): + deprecatedResult, }; late MockEditorClient mockEditorClient; @@ -145,6 +147,27 @@ void main() { }); }); + testWidgets('verify editable arguments when some are deprecated', ( + tester, + ) async { + await tester.runAsync(() async { + // Load the property editor. + await tester.pumpWidget(wrap(propertyEditor)); + final editableArgsFuture = waitForEditableArgs(); + + // Send an active location changed event. + eventController.add(activeLocationChangedEvent5); + + // Wait for the expected editable args. + final editableArgs = await editableArgsFuture; + verifyEditableArgs( + actual: editableArgs, + // Only deprecated properties with set arguments should be included. + expected: [deprecatedPropertyWithArg], + ); + }); + }); + testWidgets('verify editable arguments update when widget changes', ( tester, ) async { @@ -329,6 +352,30 @@ void main() { }); }); + group('inputs for deprecated arguments', () { + testWidgets('inputs are expected for deprecated arguments', (tester) async { + // Load the property editor. + await tester.pumpWidget(wrap(propertyEditor)); + + // Change the editable args. + controller.initForTestsOnly(editableArgsResult: deprecatedResult); + await tester.pumpAndSettle(); + + final deprecatedWithArgInput = _findDropdownButtonFormField( + 'deprecatedWithArg', + ); + + // Verify the inputs are expected. + expect(deprecatedWithArgInput, findsOneWidget); + + // Verify the labels and required are expected. + _labelsAndRequiredTextAreExpected( + deprecatedWithArgInput, + inputExpectations: deprecatedWithArgInputExpectations, + ); + }); + }); + group('filtering editable arguments', () { testWidgets('can filter by name', (tester) async { // Load the property editor. @@ -884,8 +931,17 @@ void _labelsAndRequiredTextAreExpected( shouldBeSet ? findsOneWidget : findsNothing, reason: 'Expected to find ${shouldBeSet ? 'a' : 'no'} "set" badge.', ); + // Check for the existence/non-existence of the "deprecated" badge. + final shouldBeDeprecated = inputExpectations['isDeprecated'] == true; + expect( + _labelForInput(inputFinder, matching: 'deprecated'), + shouldBeDeprecated ? findsOneWidget : findsNothing, + reason: + 'Expected to find ${shouldBeDeprecated ? 'a' : 'no'} "deprecated" badge.', + ); // Check for the existence/non-existence of the "default" badge. - final shouldBeDefault = inputExpectations['isDefault'] == true; + final shouldBeDefault = + inputExpectations['isDefault'] == true && !shouldBeDeprecated; expect( _labelForInput(inputFinder, matching: 'default'), shouldBeDefault ? findsOneWidget : findsNothing, @@ -1058,6 +1114,18 @@ final activeLocationChangedEvent4 = ActiveLocationChangedEvent( textDocument: textDocument1, ); +// Location position 5 +final activeCursorPosition5 = CursorPosition(character: 81, line: 19); +final anchorCursorPosition5 = CursorPosition(character: 113, line: 12); +final editorSelection5 = EditorSelection( + active: activeCursorPosition5, + anchor: anchorCursorPosition5, +); +final activeLocationChangedEvent5 = ActiveLocationChangedEvent( + selections: [editorSelection5], + textDocument: textDocument1, +); + final notADartDocument = TextDocument( uriAsString: '/my/fake/other.js', version: 1, @@ -1122,6 +1190,7 @@ final heightInputExpectations = { 'isSet': false, 'isRequired': false, 'isDefault': true, + 'isDeprecated': false, }; final result1 = EditableArgumentsResult( name: widgetName, @@ -1143,6 +1212,7 @@ final softWrapInputExpectations = { 'isSet': false, 'isRequired': false, 'isDefault': true, + 'isDeprecated': false, }; final alignProperty = EditableArgument.fromJson({ 'name': 'align', @@ -1169,12 +1239,49 @@ final alignInputExpectations = { 'isSet': true, 'isRequired': false, 'isDefault': false, + 'isDeprecated': false, }; final result2 = EditableArgumentsResult( name: widgetName, args: [softWrapProperty, alignProperty], ); +// Result for test cases of deprecated properties +final deprecatedPropertyNoArg = EditableArgument.fromJson({ + 'name': 'deprecatedNoArg', + 'type': 'bool', + 'isNullable': false, + 'defaultValue': false, + 'hasArgument': false, + 'isEditable': true, + 'isRequired': false, + 'isDeprecated': true, +}); + +final deprecatedPropertyWithArg = EditableArgument.fromJson({ + 'name': 'deprecatedWithArg', + 'type': 'bool', + 'value': false, + 'isNullable': false, + 'defaultValue': true, + 'hasArgument': true, + 'isEditable': true, + 'isRequired': false, + 'isDeprecated': true, +}); + +final deprecatedWithArgInputExpectations = { + 'isSet': true, + 'isRequired': false, + 'isDefault': false, + 'isDeprecated': true, +}; + +final deprecatedResult = EditableArgumentsResult( + name: widgetName, + args: [deprecatedPropertyNoArg, deprecatedPropertyWithArg], +); + // Example results for documentation test cases. final resultWithWidgetNameAndDocs = result1; final resultWithWidgetNameNoDocs = result2;