Skip to content

Commit

Permalink
Make it significant easier to create custom property matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
passsy committed Oct 16, 2023
1 parent 6af161f commit 82b723e
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
77 changes: 77 additions & 0 deletions lib/src/spot/selectors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,83 @@ extension WidgetMatcherExtensions<W extends Widget> on WidgetMatcher<W> {
}
return this;
}

WidgetMatcher<W> hasWidgetProp<T>({
required String name,
required T Function(W) prop,
required MatchProp<T> match,
}) {
final ConditionSubject<Element> conditionSubject = it<Element>();
final Subject<T> subject = conditionSubject.context.nest<T>(
() => ['$W', "with prop '$name'"],
(element) {
final widget = selector.mapElementToWidget(element);
return Extracted.value(prop.call(widget));
},
);

match(subject);
final failure = softCheck(element, conditionSubject);
if (failure != null) {
final errorParts =
describe(conditionSubject).map((it) => it.trim()).toList();
// workaround allowing to use
// hasPropertyXWhere((subject)=> subject.equals(X));
// instead of
// hasPropertyXWhere((subject)=> subject.isNotNull().equals(X));
//
// when Subject is Subject<T> but the value can actually be null (should be Subject<T?>).
final errorMessage = errorParts.join(' ');
if (errorParts.last == 'is null' &&
failure.rejection.actual.firstOrNull == '<null>') {
// property is null and isNull() was called
// not error because null == null
return this;
}
throw PropertyCheckFailure(
'Failed to match widget: $errorMessage, actual: ${failure.rejection.actual.joinToString()}',
matcherDescription: errorParts.skip(1).join(' ').removePrefix('with '),
);
}
return this;
}

WidgetMatcher<W> hasElementProp<T>({
required String name,
required T Function(Element) prop,
required MatchProp<T> match,
}) {
final ConditionSubject<Element> conditionSubject = it<Element>();
final Subject<T> subject = conditionSubject.context.nest<T>(
() => ['Element of $W', "with prop '$name'"],
(element) => Extracted.value(prop.call(element)),
);

match(subject);
final failure = softCheck(element, conditionSubject);
if (failure != null) {
final errorParts =
describe(conditionSubject).map((it) => it.trim()).toList();
// workaround allowing to use
// hasPropertyXWhere((subject)=> subject.equals(X));
// instead of
// hasPropertyXWhere((subject)=> subject.isNotNull().equals(X));
//
// when Subject is Subject<T> but the value can actually be null (should be Subject<T?>).
final errorMessage = errorParts.join(' ');
if (errorParts.last == 'is null' &&
failure.rejection.actual.firstOrNull == '<null>') {
// property is null and isNull() was called
// not error because null == null
return this;
}
throw PropertyCheckFailure(
'Failed to match widget: $errorMessage, actual: ${failure.rejection.actual.joinToString()}',
matcherDescription: errorParts.skip(1).join(' ').removePrefix('with '),
);
}
return this;
}
}

class PropertyCheckFailure extends TestFailure {
Expand Down
55 changes: 55 additions & 0 deletions test/selectors/custom_matcher_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// ignore_for_file: prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spot/spot.dart';

import '../util/assert_error.dart';

void main() {
testWidgets('match custom property', (widgetTester) async {
await widgetTester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Checkbox(
value: true,
onChanged: (_) {},
),
),
),
);

final checkbox = spotSingle<Checkbox>();

checkbox
.existsOnce()
.hasProp(
elementSelector: (e) => e.context.nest(
() => ['Checkbox', 'value'],
(e) => Extracted.value((e.widget as Checkbox).value),
),
match: (it) => it.equals(true),
)
.hasProp(
widgetSelector: (widget) => widget.context.nest(
() => ['value'],
(widget) => Extracted.value(widget.value),
),
match: (it) => it.equals(true),
)
.hasElementProp(
name: 'value',
prop: (it) => (it.widget as Checkbox).value,
match: (it) => it.equals(true),
)
.hasWidgetProp(
name: 'value',
prop: (it) => it.value,
match: (it) => it.equals(false),
);
// .hasProp(
// prop: ('value', (it) => it.value),
// match: (it) => it.equals(false),
// );
});
}

0 comments on commit 82b723e

Please sign in to comment.