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 684bfbc599d..bf949e385b0 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 @@ -20,8 +20,7 @@ class PropertyEditorView extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Property Editor', style: Theme.of(context).textTheme.titleMedium), - const PaddedDivider.noPadding(), + // TODO(elliette): Include widget name and documentation. _PropertiesList(controller: controller), ], ); @@ -84,7 +83,7 @@ class _EditablePropertyItem extends StatelessWidget { child: _PropertyInput(argument: argument, controller: controller), ), ), - if (argument.isRequired || argument.isDefault) ...[ + if (argument.hasArgument || argument.isDefault) ...[ Flexible(child: _PropertyLabels(argument: argument)), ] else const Spacer(), @@ -101,25 +100,29 @@ class _PropertyLabels extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final isRequired = argument.isRequired; + final isSet = argument.hasArgument; final isDefault = argument.isDefault; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (isRequired) + if (isSet) Padding( padding: const EdgeInsets.all(_PropertiesList.itemPadding), child: RoundedLabel( - labelText: 'required', + labelText: 'set', backgroundColor: colorScheme.primary, textColor: colorScheme.onPrimary, + tooltipText: 'Property argument is set.', ), ), if (isDefault) const Padding( padding: EdgeInsets.all(_PropertiesList.itemPadding), - child: RoundedLabel(labelText: 'default'), + child: RoundedLabel( + labelText: 'default', + tooltipText: 'Property argument matches the default value.', + ), ), ], ); @@ -143,11 +146,12 @@ class _PropertyInputState extends State<_PropertyInput> { @override Widget build(BuildContext context) { + final argument = widget.argument; final decoration = InputDecoration( - helperText: '', - errorText: widget.argument.errorText, + helperText: argument.isRequired ? '*required' : '', + errorText: argument.errorText, isDense: true, - label: Text(widget.argument.name), + label: Text('${argument.name}${argument.isRequired ? '*' : ''}'), border: const OutlineInputBorder(), ); @@ -166,6 +170,7 @@ class _PropertyInputState extends State<_PropertyInput> { return DropdownButtonFormField( value: widget.argument.valueDisplay, decoration: decoration, + isExpanded: true, items: options.toSet().toList().map((option) { return DropdownMenuItem( @@ -175,7 +180,6 @@ class _PropertyInputState extends State<_PropertyInput> { child: Text(option), ); }).toList(), - isExpanded: true, onChanged: (newValue) async { await _editArgument(newValue); }, diff --git a/packages/devtools_app/test/standalone_ui/ide_shared/property_editor_test.dart b/packages/devtools_app/test/standalone_ui/ide_shared/property_editor_test.dart index 12d64cc5c6d..51db4a4bc0a 100644 --- a/packages/devtools_app/test/standalone_ui/ide_shared/property_editor_test.dart +++ b/packages/devtools_app/test/standalone_ui/ide_shared/property_editor_test.dart @@ -137,11 +137,26 @@ void main() { controller.initForTestsOnly(editableArgs: result1.args); await tester.pumpAndSettle(); + final titleInput = _findTextFormField('title'); + final widthInput = _findTextFormField('width'); + final heightInput = _findTextFormField('height'); + // Verify the inputs are expected. expect(_findNoPropertiesMessage, findsNothing); - expect(_findTextFormField('title'), findsOneWidget); - expect(_findTextFormField('width'), findsOneWidget); - expect(_findTextFormField('height'), findsOneWidget); + expect(titleInput, findsOneWidget); + expect(widthInput, findsOneWidget); + expect(heightInput, findsOneWidget); + + // Verify the labels are expected. + expect(_labelForInput(titleInput, matching: 'set'), findsNothing); + expect(_labelForInput(titleInput, matching: 'default'), findsNothing); + expect(_labelForInput(widthInput, matching: 'set'), findsNothing); + expect(_labelForInput(widthInput, matching: 'default'), findsNothing); + expect(_labelForInput(heightInput, matching: 'set'), findsNothing); + expect(_labelForInput(heightInput, matching: 'default'), findsOneWidget); + + // Verify required comments exist. + expect(_requiredTextForInput(titleInput), findsOneWidget); }); testWidgets('inputs are expected for second group of editable arguments', ( @@ -154,12 +169,22 @@ void main() { controller.initForTestsOnly(editableArgs: result2.args); await tester.pumpAndSettle(); + final softWrapInput = _findDropdownButtonFormField('softWrap'); + final alignInput = _findDropdownButtonFormField('align'); + // Verify the inputs are expected. expect(_findNoPropertiesMessage, findsNothing); - final softWrapInput = _findDropdownButtonFormField('softWrap'); expect(softWrapInput, findsOneWidget); - final alignInput = _findDropdownButtonFormField('align'); expect(alignInput, findsOneWidget); + + // Verify the labels are expected. + expect(_labelForInput(softWrapInput, matching: 'set'), findsNothing); + expect( + _labelForInput(softWrapInput, matching: 'default'), + findsOneWidget, + ); + expect(_labelForInput(alignInput, matching: 'set'), findsOneWidget); + expect(_labelForInput(alignInput, matching: 'default'), findsNothing); }); testWidgets('softWrap input has expected options', (tester) async { @@ -243,7 +268,8 @@ void main() { await tester.pumpWidget(wrap(propertyEditor)); // Edit the title. - final titleInput = _findTextFormField('title'); + final titleInput = _findTextFormField('title*'); + expect(titleInput, findsOneWidget); await _inputText(titleInput, text: 'Brand New Title!', tester: tester); // Verify the edit is expected. @@ -317,10 +343,27 @@ final _findNoPropertiesMessage = find.text( ); Finder _findTextFormField(String inputName) => find.ancestor( - of: find.text(inputName), + of: find.textContaining(inputName), matching: find.byType(TextFormField), ); +Finder _labelForInput(Finder inputFinder, {required String matching}) { + final rowFinder = find.ancestor(of: inputFinder, matching: find.byType(Row)); + final labelFinder = find.descendant( + of: rowFinder, + matching: find.byType(RoundedLabel), + ); + return find.descendant(of: labelFinder, matching: find.text(matching)); +} + +Finder _requiredTextForInput(Finder inputFinder) => + _helperTextForInput(inputFinder, matching: '*required'); + +Finder _helperTextForInput(Finder inputFinder, {required String matching}) { + final rowFinder = find.ancestor(of: inputFinder, matching: find.byType(Row)); + return find.descendant(of: rowFinder, matching: find.richText(matching)); +} + Finder _findDropdownButtonFormField(String inputName) => find.ancestor( of: find.text(inputName), matching: find.byType(DropdownButtonFormField), @@ -446,7 +489,7 @@ final widthProperty = EditableArgument( errorText: 'Some reason for why this can\'t be edited.', isNullable: false, value: 20.0, - isRequired: true, + isRequired: false, hasArgument: false, ); final heightProperty = EditableArgument( @@ -457,7 +500,7 @@ final heightProperty = EditableArgument( isNullable: false, value: 20.0, isDefault: true, - isRequired: true, + isRequired: false, ); final result1 = EditableArgumentsResult( args: [titleProperty, widthProperty, heightProperty], diff --git a/packages/devtools_app_shared/lib/src/ui/common.dart b/packages/devtools_app_shared/lib/src/ui/common.dart index da591af5758..76751c7c581 100644 --- a/packages/devtools_app_shared/lib/src/ui/common.dart +++ b/packages/devtools_app_shared/lib/src/ui/common.dart @@ -618,17 +618,19 @@ class RoundedLabel extends StatelessWidget { required this.labelText, this.backgroundColor, this.textColor, + this.tooltipText, }); final String labelText; final Color? backgroundColor; final Color? textColor; + final String? tooltipText; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; - return Container( + final label = Container( padding: const EdgeInsets.symmetric(horizontal: denseSpacing), decoration: BoxDecoration( borderRadius: defaultBorderRadius, @@ -644,5 +646,8 @@ class RoundedLabel extends StatelessWidget { ), ), ); + return tooltipText != null + ? DevToolsTooltip(message: tooltipText, child: label) + : label; } }