createMatcherString method
Generates matchers for the properties of W
.
Implementation
String? createMatcherString({
Map<String, String> propNameOverrides = const {},
String? imports,
bool Function(DiagnosticsNode node)? filter,
}) {
final s = snapshot()..existsAtLeastOnce();
final anyElement = s.discoveredElements.first;
final elementProps = anyElement.toDiagnosticsNode().getProperties();
final widgetProps =
mapElementToWidget(anyElement).toDiagnosticsNode().getProperties();
String widgetType = _typeOf<W>().toString().capitalize();
if (widgetType.contains('<')) {
widgetType = widgetType.substring(0, widgetType.indexOf('<'));
}
bool addedMethods = false;
final matcherSb = StringBuffer();
matcherSb.writeln('''
/// Matchers for the properties of [$widgetType] provided via [Diagnosticable.debugFillProperties]
extension ${widgetType}Matcher on WidgetMatcher<$widgetType> {
''');
final selectorSb = StringBuffer();
selectorSb.writeln(
'''
/// Allows filtering [$widgetType] by the properties provided via [Diagnosticable.debugFillProperties]
extension ${widgetType}Selector on WidgetSelector<$widgetType> {
''',
);
final getterSb = StringBuffer();
getterSb.writeln(
'''
/// Retrieves the [DiagnosticsProperty] of the matched widget with [propName] of type [T]
extension ${widgetType}Getter on WidgetMatcher<$widgetType> {
''',
);
final distinctProps =
[...widgetProps, ...elementProps].distinctBy((it) => it.name).toList();
for (final DiagnosticsNode prop in distinctProps) {
if (filter != null && !filter(prop)) {
continue;
}
final String diagnosticPropName = prop.name!;
final String methodPropName = () {
final String name = prop.name!;
final parts = name.split(RegExp('[^a-zA-Z]'));
if (parts.length == 1) {
return name;
}
// camel case
return parts
.mapIndexed((index, it) => index == 0 ? it : it.capitalize())
.join();
}();
final humanPropName = propNameOverrides[methodPropName] ?? methodPropName;
String propType = prop.getType();
if (prop is ObjectFlagProperty &&
(propType == 'Widget' || propType == 'Widget?')) {
// matchers on widgets are not supported, use .spot() to check the tree further down
continue;
}
if (prop is FlagProperty && methodPropName == 'dirty') {
// dirty flags are irrelevant for assertions (and always false)
continue;
}
if (propType.contains('=>')) {
// ignore lambda properties
continue;
}
if (prop.name == 'depth' || prop.name == 'key') {
// ignore default properties that are covered by general Wiget selectors
continue;
}
if (prop.name == 'dependencies') {
if (propType == 'List<DiagnosticsNode>' ||
propType == 'Set<InheritedElement>') {
// Widget dependencies are only indirect properties
continue;
}
}
if (prop.name == 'renderObject' && propType == 'RenderObject') {
final propValueRuntimeType = prop.value.runtimeType.toString();
if (!propValueRuntimeType.startsWith('_')) {
propType = propValueRuntimeType;
}
}
if (prop.name == 'state' && propType.contains('State<StatefulWidget>')) {
final propValueRuntimeType = prop.value.runtimeType.toString();
if (propValueRuntimeType.startsWith('_')) {
// this is not useful without type
continue;
}
propType = propValueRuntimeType;
continue;
}
final propTypeNullable = propType.endsWith('?') ? propType : '$propType?';
String matcherVerb = 'has';
if (humanPropName == 'enabled') {
matcherVerb = 'is';
}
addedMethods = true;
final widgetMatcherWithValueName =
'$matcherVerb${humanPropName.capitalize()}';
final widgetMatcherWithPropName = '${widgetMatcherWithValueName}Where';
final String valueExample = _getExampleValue(node: prop);
final String matcherExample = _getExampleValue(node: prop, matcher: true);
getterSb.writeln('''
/// Returns the $humanPropName of the matched [$widgetType] via [Widget.toDiagnosticsNode]
$propType get${humanPropName.capitalize()}() {
return getDiagnosticProp<$propType>('$diagnosticPropName');
}
''');
matcherSb.writeln('''
/// Expects that $humanPropName of [$widgetType] matches the condition in [match].
///
/// #### Example usage:
/// ```dart
/// spot<$widgetType>().existsOnce().$widgetMatcherWithPropName($matcherExample);
/// ```
WidgetMatcher<$widgetType> $widgetMatcherWithPropName(MatchProp<$propType> match) {
return hasDiagnosticProp<$propType>('$diagnosticPropName', match);
}
/// Expects that $humanPropName of [$widgetType] equals (==) [value].
///
/// #### Example usage:
/// ```dart
/// spot<$widgetType>().existsOnce().$widgetMatcherWithValueName($valueExample);
/// ```
WidgetMatcher<$widgetType> $widgetMatcherWithValueName($propTypeNullable value) {
return hasDiagnosticProp<$propType>('$diagnosticPropName', (it) => value == null ? it.isNull() : it.equals(value));
}
''');
final propPart = humanPropName.capitalize();
selectorSb.writeln('''
/// Creates a [WidgetSelector] that finds all [$widgetType] where $humanPropName matches the condition.
///
/// #### Example usage:
/// ```dart
/// spot<$widgetType>().where$propPart($matcherExample).existsOnce();
/// ```
@useResult
WidgetSelector<$widgetType> where$propPart(MatchProp<$propType> match) {
return withDiagnosticProp<$propType>('$diagnosticPropName', match);
}
/// Creates a [WidgetSelector] that finds all [$widgetType] where $humanPropName equals (==) [value].
///
/// #### Example usage:
/// ```dart
/// spot<$widgetType>().with$propPart($valueExample).existsOnce();
/// ```
@useResult
WidgetSelector<$widgetType> with$propPart($propTypeNullable value) {
return withDiagnosticProp<$propType>('$diagnosticPropName', (it) => value == null ? it.isNull() : it.equals(value));
}
''');
}
matcherSb.writeln('}');
selectorSb.writeln('}');
getterSb.writeln('}');
if (addedMethods == false) {
// nothing added, don't generate the file at all
return null;
}
final overridesParam = propNameOverrides.isEmpty
? ''
: () {
final map = propNameOverrides
.mapEntries((it) => MapEntry("'${it.key}'", "'${it.value}'"))
.map((it) => '${it.key}: ${it.value}')
.joinToString(separator: ', ', prefix: '{', postfix: '}');
return 'propNameOverrides: $map';
}();
return '''
// ignore_for_file: require_trailing_commas, directives_ordering
\/\/ coverage:ignore-file
/// Matchers for [$widgetType] auto-generated by spot
///
/// Can be generated with:
/// ```dart
/// spot<$widgetType>().printMatchers($overridesParam);
/// ```
library;
import 'package:flutter/foundation.dart';
${imports ?? "import 'package:flutter/widgets.dart';"}
import 'package:spot/spot.dart';
$selectorSb
$matcherSb
$getterSb
''';
}