Add IterableFlagsProperty and use it on proxy box classes (#39354)
* Add FlagsSummary and implement Listener
This commit is contained in:
@@ -2308,13 +2308,17 @@ class EnumProperty<T> extends DiagnosticsProperty<T> {
|
||||
/// omitted, that is taken to mean that [level] should be
|
||||
/// [DiagnosticLevel.hidden] when [value] is non-null or null respectively.
|
||||
///
|
||||
/// This kind of diagnostics property is typically used for values mostly opaque
|
||||
/// This kind of diagnostics property is typically used for opaque
|
||||
/// values, like closures, where presenting the actual object is of dubious
|
||||
/// value but where reporting the presence or absence of the value is much more
|
||||
/// useful.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
///
|
||||
/// * [FlagsSummary], which provides similar functionality but accepts multiple
|
||||
/// flags under the same name, and is preferred if there are multiple such
|
||||
/// values that can fit into a same category (such as "listeners").
|
||||
/// * [FlagProperty], which provides similar functionality describing whether
|
||||
/// a [value] is true or false.
|
||||
class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
|
||||
@@ -2415,6 +2419,106 @@ class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A summary of multiple properties, indicating whether each of them is present
|
||||
/// (non-null) or absent (null).
|
||||
///
|
||||
/// Each entry of [value] is described by its key. The eventual description will
|
||||
/// be a list of keys of non-null entries.
|
||||
///
|
||||
/// The [ifEmpty] describes the entire collection of [value] when it contains no
|
||||
/// non-null entries. If [ifEmpty] is omitted, [level] will be
|
||||
/// [DiagnosticLevel.hidden] when [value] contains no non-null entries.
|
||||
///
|
||||
/// This kind of diagnostics property is typically used for opaque
|
||||
/// values, like closures, where presenting the actual object is of dubious
|
||||
/// value but where reporting the presence or absence of the value is much more
|
||||
/// useful.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ObjectFlagSummary], which provides similar functionality but accepts
|
||||
/// only one flag, and is preferred if there is only one entry.
|
||||
/// * [IterableProperty], which provides similar functionality describing
|
||||
/// the values a collection of objects.
|
||||
class FlagsSummary<T> extends DiagnosticsProperty<Map<String, T>> {
|
||||
/// Create a summary for multiple properties, indicating whether each of them
|
||||
/// is present (non-null) or absent (null).
|
||||
///
|
||||
/// The [value], [showName], [showSeparator] and [level] arguments must not be
|
||||
/// null.
|
||||
FlagsSummary(
|
||||
String name,
|
||||
Map<String, T> value, {
|
||||
String ifEmpty,
|
||||
bool showName = true,
|
||||
bool showSeparator = true,
|
||||
DiagnosticLevel level = DiagnosticLevel.info,
|
||||
}) : assert(value != null),
|
||||
assert(showName != null),
|
||||
assert(showSeparator != null),
|
||||
assert(level != null),
|
||||
super(
|
||||
name,
|
||||
value,
|
||||
ifEmpty: ifEmpty,
|
||||
showName: showName,
|
||||
showSeparator: showSeparator,
|
||||
level: level,
|
||||
);
|
||||
|
||||
@override
|
||||
String valueToString({TextTreeConfiguration parentConfiguration}) {
|
||||
assert(value != null);
|
||||
if (!_hasNonNullEntry() && ifEmpty != null)
|
||||
return ifEmpty;
|
||||
|
||||
final Iterable<String> formattedValues = _formattedValues();
|
||||
if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
|
||||
// Always display the value as a single line and enclose the iterable
|
||||
// value in brackets to avoid ambiguity.
|
||||
return '[${formattedValues.join(', ')}]';
|
||||
}
|
||||
|
||||
return formattedValues.join(_isSingleLine(style) ? ', ' : '\n');
|
||||
}
|
||||
|
||||
/// Priority level of the diagnostic used to control which diagnostics should
|
||||
/// be shown and filtered.
|
||||
///
|
||||
/// If [ifEmpty] is null and the [value] contains no non-null entries, then
|
||||
/// level [DiagnosticLevel.hidden] is returned.
|
||||
@override
|
||||
DiagnosticLevel get level {
|
||||
if (!_hasNonNullEntry() && ifEmpty == null)
|
||||
return DiagnosticLevel.hidden;
|
||||
return super.level;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Object> toJsonMap(DiagnosticsSerializationDelegate delegate) {
|
||||
final Map<String, Object> json = super.toJsonMap(delegate);
|
||||
if (value.isNotEmpty)
|
||||
json['values'] = _formattedValues().toList();
|
||||
return json;
|
||||
}
|
||||
|
||||
bool _hasNonNullEntry() => value.values.any((Object o) => o != null);
|
||||
|
||||
// An iterable of each entry's description in [value].
|
||||
//
|
||||
// For a non-null value, its description is its key.
|
||||
//
|
||||
// For a null value, it is omitted unless `includeEmtpy` is true and
|
||||
// [ifEntryNull] contains a corresponding description.
|
||||
Iterable<String> _formattedValues() sync* {
|
||||
for (MapEntry<String, T> entry in value.entries) {
|
||||
if (entry.value != null) {
|
||||
yield entry.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signature for computing the value of a property.
|
||||
///
|
||||
/// May throw exception if accessing the property would throw an exception
|
||||
|
||||
@@ -2575,21 +2575,17 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
final List<String> listeners = <String>[];
|
||||
if (onPointerDown != null)
|
||||
listeners.add('down');
|
||||
if (onPointerMove != null)
|
||||
listeners.add('move');
|
||||
if (onPointerUp != null)
|
||||
listeners.add('up');
|
||||
if (onPointerCancel != null)
|
||||
listeners.add('cancel');
|
||||
if (onPointerSignal != null)
|
||||
listeners.add('signal');
|
||||
if (listeners.isEmpty)
|
||||
listeners.add('<none>');
|
||||
properties.add(IterableProperty<String>('listeners', listeners));
|
||||
// TODO(jacobr): add raw listeners to the diagnostics data.
|
||||
properties.add(FlagsSummary<Function>(
|
||||
'listeners',
|
||||
<String, Function>{
|
||||
'down': onPointerDown,
|
||||
'move': onPointerMove,
|
||||
'up': onPointerUp,
|
||||
'cancel': onPointerCancel,
|
||||
'signal': onPointerSignal,
|
||||
},
|
||||
ifEmpty: '<none>',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2773,17 +2769,15 @@ class RenderMouseRegion extends RenderProxyBox {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
final List<String> listeners = <String>[];
|
||||
if (onEnter != null)
|
||||
listeners.add('enter');
|
||||
if (onHover != null)
|
||||
listeners.add('hover');
|
||||
if (onExit != null)
|
||||
listeners.add('exit');
|
||||
if (listeners.isEmpty)
|
||||
listeners.add('<none>');
|
||||
properties.add(IterableProperty<String>('listeners', listeners));
|
||||
// TODO(jacobr): add raw listeners to the diagnostics data.
|
||||
properties.add(FlagsSummary<Function>(
|
||||
'listeners',
|
||||
<String, Function>{
|
||||
'enter': onEnter,
|
||||
'hover': onHover,
|
||||
'exit': onExit,
|
||||
},
|
||||
ifEmpty: '<none>',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,21 @@ void validateObjectFlagPropertyJsonSerialization(ObjectFlagProperty<Object> prop
|
||||
validatePropertyJsonSerializationHelper(json, property);
|
||||
}
|
||||
|
||||
void validateIterableFlagsPropertyJsonSerialization(FlagsSummary<Object> property) {
|
||||
final Map<String, Object> json = simulateJsonSerialization(property);
|
||||
if (property.value.isNotEmpty) {
|
||||
expect(json['values'], equals(
|
||||
property.value.entries
|
||||
.where((MapEntry<String, Object> entry) => entry.value != null)
|
||||
.map((MapEntry<String, Object> entry) => entry.key).toList(),
|
||||
));
|
||||
} else {
|
||||
expect(json.containsKey('values'), isFalse);
|
||||
}
|
||||
|
||||
validatePropertyJsonSerializationHelper(json, property);
|
||||
}
|
||||
|
||||
void validateIterablePropertyJsonSerialization(IterableProperty<Object> property) {
|
||||
final Map<String, Object> json = simulateJsonSerialization(property);
|
||||
if (property.value != null) {
|
||||
@@ -1601,6 +1616,88 @@ void main() {
|
||||
validateObjectFlagPropertyJsonSerialization(missing);
|
||||
});
|
||||
|
||||
test('iterable flags property test', () {
|
||||
// Normal property
|
||||
{
|
||||
final Function onClick = () { };
|
||||
final Function onMove = () { };
|
||||
final Map<String, Function> value = <String, Function>{
|
||||
'click': onClick,
|
||||
'move': onMove,
|
||||
};
|
||||
final FlagsSummary<Function> flags = FlagsSummary<Function>(
|
||||
'listeners',
|
||||
value,
|
||||
);
|
||||
expect(flags.name, equals('listeners'));
|
||||
expect(flags.value, equals(value));
|
||||
expect(flags.isFiltered(DiagnosticLevel.info), isFalse);
|
||||
expect(flags.toString(), equals('listeners: click, move'));
|
||||
validateIterableFlagsPropertyJsonSerialization(flags);
|
||||
}
|
||||
|
||||
// Reversed-order property
|
||||
{
|
||||
final Function onClick = () { };
|
||||
final Function onMove = () { };
|
||||
final Map<String, Function> value = <String, Function>{
|
||||
'move': onMove,
|
||||
'click': onClick,
|
||||
};
|
||||
final FlagsSummary<Function> flags = FlagsSummary<Function>(
|
||||
'listeners',
|
||||
value,
|
||||
);
|
||||
expect(flags.toString(), equals('listeners: move, click'));
|
||||
expect(flags.isFiltered(DiagnosticLevel.info), isFalse);
|
||||
validateIterableFlagsPropertyJsonSerialization(flags);
|
||||
}
|
||||
|
||||
// Partially empty property
|
||||
{
|
||||
final Function onClick = () { };
|
||||
final Map<String, Function> value = <String, Function>{
|
||||
'move': null,
|
||||
'click': onClick,
|
||||
};
|
||||
final FlagsSummary<Function> flags = FlagsSummary<Function>(
|
||||
'listeners',
|
||||
value,
|
||||
);
|
||||
expect(flags.toString(), equals('listeners: click'));
|
||||
expect(flags.isFiltered(DiagnosticLevel.info), isFalse);
|
||||
validateIterableFlagsPropertyJsonSerialization(flags);
|
||||
}
|
||||
|
||||
// Empty property (without ifEmpty)
|
||||
{
|
||||
final Map<String, Function> value = <String, Function>{
|
||||
'enter': null,
|
||||
};
|
||||
final FlagsSummary<Function> flags = FlagsSummary<Function>(
|
||||
'listeners',
|
||||
value,
|
||||
);
|
||||
expect(flags.isFiltered(DiagnosticLevel.info), isTrue);
|
||||
validateIterableFlagsPropertyJsonSerialization(flags);
|
||||
}
|
||||
|
||||
// Empty property (without ifEmpty)
|
||||
{
|
||||
final Map<String, Function> value = <String, Function>{
|
||||
'enter': null,
|
||||
};
|
||||
final FlagsSummary<Function> flags = FlagsSummary<Function>(
|
||||
'listeners',
|
||||
value,
|
||||
ifEmpty: '<none>',
|
||||
);
|
||||
expect(flags.toString(), equals('listeners: <none>'));
|
||||
expect(flags.isFiltered(DiagnosticLevel.info), isFalse);
|
||||
validateIterableFlagsPropertyJsonSerialization(flags);
|
||||
}
|
||||
});
|
||||
|
||||
test('iterable property test', () {
|
||||
final List<int> ints = <int>[1,2,3];
|
||||
final IterableProperty<int> intsProperty = IterableProperty<int>(
|
||||
|
||||
@@ -353,6 +353,50 @@ void main() {
|
||||
expect(events.single.transform, expectedTransform);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('RenderPointerListener\'s debugFillProperties when default', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
RenderPointerListener().debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'parentData: MISSING',
|
||||
'constraints: MISSING',
|
||||
'size: MISSING',
|
||||
'behavior: deferToChild',
|
||||
'listeners: <none>'
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('RenderPointerListener\'s debugFillProperties when full', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
RenderPointerListener(
|
||||
onPointerDown: (PointerDownEvent event) {},
|
||||
onPointerUp: (PointerUpEvent event) {},
|
||||
onPointerMove: (PointerMoveEvent event) {},
|
||||
onPointerCancel: (PointerCancelEvent event) {},
|
||||
onPointerSignal: (PointerSignalEvent event) {},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: RenderErrorBox(),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'parentData: MISSING',
|
||||
'constraints: MISSING',
|
||||
'size: MISSING',
|
||||
'behavior: opaque',
|
||||
'listeners: down, move, up, cancel, signal'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> scrollAt(Offset position, WidgetTester tester) {
|
||||
|
||||
@@ -689,6 +689,45 @@ void main() {
|
||||
await gesture.removePointer();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('RenderMouseRegion\'s debugFillProperties when default', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
RenderMouseRegion().debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'parentData: MISSING',
|
||||
'constraints: MISSING',
|
||||
'size: MISSING',
|
||||
'listeners: <none>'
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('RenderMouseRegion\'s debugFillProperties when full', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
RenderMouseRegion(
|
||||
onEnter: (PointerEnterEvent event) {},
|
||||
onExit: (PointerExitEvent event) {},
|
||||
onHover: (PointerHoverEvent event) {},
|
||||
child: RenderErrorBox(),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'parentData: MISSING',
|
||||
'constraints: MISSING',
|
||||
'size: MISSING',
|
||||
'listeners: enter, hover, exit'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
// This widget allows you to send a callback that is called during `onPaint.
|
||||
|
||||
Reference in New Issue
Block a user