Skip to content

Take Condition for async nesting expectations #1896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
28eeb2a
Take Condition for async nesting expectations
natebosch Feb 2, 2023
fe40a54
Rename asyncDescription
natebosch Feb 2, 2023
9f25f8f
Merge branch 'master' into async-take-condition-drop-which
natebosch Feb 2, 2023
8ab184b
Make `label` arguments take callbacks
natebosch Feb 3, 2023
7a0724f
Expand the doc comment for Context
natebosch Feb 3, 2023
10e6f47
Compare the non-nesting label case to a clause directly
natebosch Feb 3, 2023
bac2b3e
Mention that which can be omitted
natebosch Feb 3, 2023
8529702
dart format
natebosch Feb 3, 2023
4e4cf10
Enforce single string still in root ctor
natebosch Feb 4, 2023
00888e6
Rename _StringClause to _ExpectationClause
natebosch Feb 4, 2023
308b13d
Update docs for nest and nestAsync
natebosch Feb 4, 2023
e35574b
Consistently use double quotes to avoid escape single quote
natebosch Feb 4, 2023
915652d
Merge commit 'e35574bb' into label-callback--context-docs
natebosch Feb 4, 2023
511fa70
Merge branch 'master' into label-callback--context-docs
natebosch Feb 4, 2023
911659b
Attempt to present information in a better order
natebosch Feb 4, 2023
547abd3
More wording tweaks, add doc header on ConditionSubject
natebosch Feb 4, 2023
ef6bd7e
More wording tweaks and a paragraph on softCheck subjects
natebosch Feb 4, 2023
99f9e2f
Move the 'should not throw' phrase into a template that is used in th…
natebosch Feb 4, 2023
9400186
Merge branch 'master' into async-take-condition-drop-which
natebosch Feb 4, 2023
5a09c33
Merge branch 'master' into async-take-condition-drop-which
natebosch Feb 4, 2023
2ddbd46
Tweaks
natebosch Feb 4, 2023
d215a20
Merge branch 'label-callback--context-docs' into async-take-condition…
natebosch Feb 4, 2023
7c9fce9
Add missing generic, use {} over =>
natebosch Feb 4, 2023
3ca1aaf
Make the fields of Extracted private
natebosch Feb 4, 2023
db4fc6f
More working tweaks, export ConditionSubject
natebosch Feb 4, 2023
7890a64
Merge branch 'label-callback--context-docs' into async-take-condition…
natebosch Feb 4, 2023
b33a327
Merge branch 'master' into label-callback--context-docs
natebosch Feb 4, 2023
9d7fb07
Merge branch 'label-callback--context-docs' into async-take-condition…
natebosch Feb 4, 2023
6b38bff
Typo
natebosch Feb 4, 2023
330c3f6
Expectation extension method in Subject doc
natebosch Feb 4, 2023
4d14182
missing word
natebosch Feb 6, 2023
6a8e670
Link to example extensions, expand callback phrasing
natebosch Feb 6, 2023
afc09b3
Link the specific methods earlier
natebosch Feb 6, 2023
148ef9d
Move section on callbacks being unused to much later
natebosch Feb 6, 2023
bae48cb
Add some examples
natebosch Feb 6, 2023
e09f2ae
Revert mistaken change
natebosch Feb 6, 2023
b9b8960
Merge branch 'master' into label-callback--context-docs
natebosch Feb 6, 2023
f17581f
Merge branch 'label-callback--context-docs' into async-take-condition…
natebosch Feb 6, 2023
bfc06ed
Merge branch 'master' into async-take-condition-drop-which
natebosch Feb 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pkgs/checks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
- `checkThat` renamed to `check`.
- `nest` and `nestAsync` take `Iterable<String> Function()` arguments for
`label` instead of `String`.
- Async expectation extensions `completes`, `throws`, `emits`, and
`emitsError` no longer return a `Future<Subject>`. Instead they take an
optional `Condition` argument which can check expectations that would
have been checked on the returned subject.
- `nestAsync` no longer returns a `Subject`, callers must pass the
followup `Condition` to the nullable argument.
- Remove the `which` extension on `Future<Subject>`.
- Add a constructor for `Condition` which takes a callback to invoke when
`apply` or `applyAsync` is called.
- Added an example.
- Include a stack trace in the failure description for unexpected errors from
Futures or Streams.
Expand Down
9 changes: 2 additions & 7 deletions pkgs/checks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ condition. The `it()` utility returns a `ConditionSubject`.
check(someList).any(it()..isGreaterThan(0));
```

Some complicated checks may be difficult to write with parenthesized awaited
expressions, or impossible to write with cascade syntax. There are `which`
utilities for both use cases which take a `Condition`.
Some complicated checks may be not be possible to write with cascade syntax.
There is a `which` utility for this use case which takes a `Condition`.

```dart
check(someString)
Expand All @@ -61,10 +60,6 @@ check(someString)
..length.which(it()
..isGreatherThan(10)
..isLessThan(100));

await check(someFuture)
.completes()
.which(it()..equals(expectedCompletion));
```

# Writing custom expectations
Expand Down
2 changes: 1 addition & 1 deletion pkgs/checks/lib/checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

export 'src/checks.dart' show SkipExtension, Subject, check, it;
export 'src/extensions/async.dart'
show ChainAsync, FutureChecks, StreamChecks, WithQueueExtension;
show FutureChecks, StreamChecks, WithQueueExtension;
export 'src/extensions/core.dart' show BoolChecks, CoreChecks, NullableChecks;
export 'src/extensions/function.dart' show FunctionChecks;
export 'src/extensions/iterable.dart' show IterableChecks;
Expand Down
65 changes: 43 additions & 22 deletions pkgs/checks/lib/src/checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,17 @@ Future<Iterable<String>> describeAsync<T>(Condition<T> condition) async {

/// A set of expectations that are checked against the value when applied to a
/// [Subject].
///
/// This class should not be implemented or extended.
abstract class Condition<T> {
/// Check the expectations of this condition against [subject].
///
/// The [subject] should throw if any asynchronous expectations are checked.
/// It is not possible to wait for for asynchronous expectations to be fully
/// applied with this method.
void apply(Subject<T> subject);

/// Check the expectations of this condition against [subject].
Future<void> applyAsync(Subject<T> subject);
}

Expand Down Expand Up @@ -481,6 +490,9 @@ abstract class Context<T> {
/// [Extracted.rejection] describing the problem. Otherwise it should return
/// an [Extracted.value].
///
/// Subsequent expectations can be checked for the extracted value on the
/// returned [Subject].
///
/// {@macro label_description}
///
/// If [atSameLevel] is true then the returned [Extracted.value] should hold
Expand Down Expand Up @@ -518,6 +530,10 @@ abstract class Context<T> {
/// [Extracted.rejection] describing the problem. Otherwise it should return
/// an [Extracted.value].
///
/// In contrast to [nest], subsequent expectations need to be passed in
/// [nestedCondition] which will be applied to the subject for the extracted
/// value.
///
/// {@macro label_description}
///
/// {@macro description_lines}
Expand All @@ -527,17 +543,19 @@ abstract class Context<T> {
/// {@macro async_limitations}
///
/// ```dart
/// Future<Subject<Foo>> get someAsyncValue async => await context
/// .nestAsync(() => ['has someAsyncValue'], (actual) async {
/// if (await _cannotReadAsyncValue(actual)) {
/// return Extracted.rejection(
/// which: ['cannot read someAsyncValue']);
/// }
/// return Extracted.value(await _readAsyncValue(actual));
/// });
/// Future<void> someAsyncResult([Condition<Result> resultCondition]) async {
/// await context.nestAsync(() => ['has someAsyncResult'], (actual) async {
/// if (await _asyncOperationFailed(actual)) {
/// return Extracted.rejection(which: ['cannot read someAsyncResult']);
/// }
/// return Extracted.value(await _readAsyncResult(actual));
/// }, resultCondition);
/// }
/// ```
Future<Subject<R>> nestAsync<R>(Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T) extract);
Future<void> nestAsync<R>(
Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T) extract,
Condition<R>? nestedCondition);
}

/// A property extracted from a value being checked, or a rejection.
Expand Down Expand Up @@ -752,8 +770,10 @@ class _TestContext<T> implements Context<T>, _ClauseDescription {
}

@override
Future<Subject<R>> nestAsync<R>(Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T) extract) async {
Future<void> nestAsync<R>(
Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T) extract,
Condition<R>? nestedCondition) async {
if (!_allowAsync) {
throw StateError(
'Async expectations cannot be used on a synchronous subject');
Expand All @@ -770,7 +790,7 @@ class _TestContext<T> implements Context<T>, _ClauseDescription {
final value = result._value ?? _Absent<R>();
final context = _TestContext<R>._child(value, label, this);
_clauses.add(context);
return Subject._(context);
await nestedCondition?.applyAsync(Subject<R>._(context));
}

CheckFailure _failure(Rejection rejection) =>
Expand Down Expand Up @@ -843,9 +863,11 @@ class _SkippedContext<T> implements Context<T> {
}

@override
Future<Subject<R>> nestAsync<R>(Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T p1) extract) async {
return Subject._(_SkippedContext());
Future<void> nestAsync<R>(
Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T p1) extract,
Condition<R>? nestedCondition) async {
// no-op
}
}

Expand Down Expand Up @@ -1066,13 +1088,12 @@ class _ReplayContext<T> implements Context<T>, Condition<T> {
}

@override
Future<Subject<R>> nestAsync<R>(Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T) extract) async {
final nestedContext = _ReplayContext<R>();
Future<void> nestAsync<R>(
Iterable<String> Function() label,
FutureOr<Extracted<R>> Function(T) extract,
Condition<R>? nestedCondition) async {
_interactions.add((c) async {
var result = await c.nestAsync(label, extract);
await nestedContext.applyAsync(result);
await c.nestAsync(label, extract, nestedCondition);
});
return Subject._(nestedContext);
}
}
Loading