Unverified Commit 4fc11db5 authored by Tong Mu's avatar Tong Mu Committed by GitHub

Add IterableFlagsProperty and use it on proxy box classes (#39354)

* Add FlagsSummary and implement Listener
parent 9f4ab273
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment