From 2d199ed8c75bcac67ae6cd763431d25741efc802 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 2 Feb 2023 03:35:26 +0000 Subject: [PATCH 1/2] Add a top level equals utility As a shortcut for the very common `it()..equals` pattern. Any other condition will still need to fall back on `it()..`. --- pkgs/checks/README.md | 2 +- pkgs/checks/lib/checks.dart | 2 +- pkgs/checks/lib/src/extensions/async.dart | 6 +- pkgs/checks/lib/src/extensions/core.dart | 7 ++ pkgs/checks/test/describe_test.dart | 2 +- pkgs/checks/test/extensions/async_test.dart | 118 +++++++++--------- pkgs/checks/test/extensions/core_test.dart | 2 +- .../checks/test/extensions/iterable_test.dart | 14 +-- pkgs/checks/test/extensions/map_test.dart | 8 +- pkgs/checks/test/extensions/string_test.dart | 43 +++---- 10 files changed, 101 insertions(+), 103 deletions(-) diff --git a/pkgs/checks/README.md b/pkgs/checks/README.md index f87835346..1c06f6833 100644 --- a/pkgs/checks/README.md +++ b/pkgs/checks/README.md @@ -64,7 +64,7 @@ checkThat(someString) await checkThat(someFuture) .completes() - .which(it()..equals(expectedCompletion)); + .which(equals(expectedCompletion)); ``` # Writing custom expectations diff --git a/pkgs/checks/lib/checks.dart b/pkgs/checks/lib/checks.dart index 5eb6a854b..59cfb5f16 100644 --- a/pkgs/checks/lib/checks.dart +++ b/pkgs/checks/lib/checks.dart @@ -6,7 +6,7 @@ export 'src/checks.dart' show checkThat, Subject, Skip, it; export 'src/extensions/async.dart' show ChainAsync, FutureChecks, StreamChecks, StreamQueueWrap; export 'src/extensions/core.dart' - show BoolChecks, CoreChecks, NullabilityChecks; + show BoolChecks, CoreChecks, NullabilityChecks, equals; export 'src/extensions/function.dart' show ThrowsCheck; export 'src/extensions/iterable.dart' show IterableChecks; export 'src/extensions/map.dart' show MapChecks; diff --git a/pkgs/checks/lib/src/extensions/async.dart b/pkgs/checks/lib/src/extensions/async.dart index 90181dfb7..ff54f4efa 100644 --- a/pkgs/checks/lib/src/extensions/async.dart +++ b/pkgs/checks/lib/src/extensions/async.dart @@ -206,8 +206,8 @@ extension StreamChecks on Subject> { /// /// ```dart /// await checkThat(StreamQueue(someStream)).inOrder([ - /// it()..emits().that(it()..equals(0)), - /// it()..emits().that(it()..equals(1)), + /// it()..emits().that(equals(0)), + /// it()..emits().that(equals(1)), // ]); /// ``` /// @@ -445,7 +445,7 @@ extension ChainAsync on Future> { /// expression that would need parenthesis. /// /// ```dart - /// await checkThat(someFuture).completes().which(it()..equals('expected')); + /// await checkThat(someFuture).completes().which(equals('expected')); /// // or, with the intermediate `await`: /// (await checkThat(someFuture).completes()).equals('expected'); /// ``` diff --git a/pkgs/checks/lib/src/extensions/core.dart b/pkgs/checks/lib/src/extensions/core.dart index a2575ee69..91a973015 100644 --- a/pkgs/checks/lib/src/extensions/core.dart +++ b/pkgs/checks/lib/src/extensions/core.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:checks/checks.dart'; import 'package:checks/context.dart'; extension CoreChecks on Subject { @@ -96,6 +97,12 @@ extension CoreChecks on Subject { } } +/// Returns a [Condition] checking that the actual value is equal to [expected] +/// by operator `==`. +/// +/// This is a shortcut for `it()..equals(expected)`. +Condition equals(T expected) => it()..equals(expected); + extension BoolChecks on Subject { void isTrue() { context.expect( diff --git a/pkgs/checks/test/describe_test.dart b/pkgs/checks/test/describe_test.dart index 1fd239c6b..b9b335c8e 100644 --- a/pkgs/checks/test/describe_test.dart +++ b/pkgs/checks/test/describe_test.dart @@ -12,7 +12,7 @@ void main() { checkThat(describe(it())).isEmpty(); }); test('includes condition clauses', () { - checkThat(describe(it()..equals(1))).deepEquals([' equals <1>']); + checkThat(describe(equals(1))).deepEquals([' equals <1>']); }); test('includes nested clauses', () { checkThat(describe(it()..length.equals(1))).deepEquals([ diff --git a/pkgs/checks/test/extensions/async_test.dart b/pkgs/checks/test/extensions/async_test.dart index 629989cd7..280ba54b3 100644 --- a/pkgs/checks/test/extensions/async_test.dart +++ b/pkgs/checks/test/extensions/async_test.dart @@ -16,11 +16,11 @@ void main() { group('FutureChecks', () { group('completes', () { test('succeeds for a future that completes to a value', () async { - await checkThat(_futureSuccess()).completes().which(it()..equals(42)); + await checkThat(_futureSuccess()).completes().which(equals(42)); }); test('rejects futures which complete as errors', () async { await checkThat(_futureFail()).isRejectedByAsync( - it()..completes().which(it()..equals(1)), + it()..completes().which(equals(1)), actual: ['a future that completes as an error'], which: ['threw at:', 'fake trace'], ); @@ -29,7 +29,7 @@ void main() { await checkThat(it>()..completes()) .asyncDescription .which(it()..deepEquals([' completes to a value'])); - await checkThat(it>()..completes().which(it()..equals(42))) + await checkThat(it>()..completes().which(equals(42))) .asyncDescription .which(it() ..deepEquals([ @@ -131,7 +131,7 @@ fake trace'''); group('StreamChecks', () { group('emits', () { test('succeeds for a stream that emits a value', () async { - await checkThat(_countingStream(5)).emits().which(it()..equals(0)); + await checkThat(_countingStream(5)).emits().which(equals(0)); }); test('fails for a stream that closes without emitting', () async { await checkThat(_countingStream(0)).isRejectedByAsync( @@ -151,7 +151,7 @@ fake trace'''); await checkThat(it>()..emits()) .asyncDescription .which(it()..deepEquals([' emits a value'])); - await checkThat(it>()..emits().which(it()..equals(42))) + await checkThat(it>()..emits().which(equals(42))) .asyncDescription .which(it() ..deepEquals([ @@ -215,25 +215,25 @@ fake trace'''); test('uses a transaction', () async { final queue = _countingStream(1); await softCheckAsync>(queue, it()..emitsError()); - await checkThat(queue).emits().which((it()..equals(0))); + await checkThat(queue).emits().which((equals(0))); }); }); group('emitsThrough', () { test('succeeds for a stream that eventuall emits a matching value', () async { - await checkThat(_countingStream(5)).emitsThrough(it()..equals(4)); + await checkThat(_countingStream(5)).emitsThrough(equals(4)); }); test('fails for a stream that closes without emitting a matching value', () async { await checkThat(_countingStream(4)).isRejectedByAsync( - it()..emitsThrough(it()..equals(5)), + it()..emitsThrough(equals(5)), actual: ['a stream'], which: ['ended after emitting 4 elements with none matching'], ); }); test('can be described', () async { - await checkThat(it>()..emitsThrough(it()..equals(42))) + await checkThat(it>()..emitsThrough(equals(42))) .asyncDescription .which(it() ..deepEquals([ @@ -244,21 +244,21 @@ fake trace'''); test('uses a transaction', () async { final queue = _countingStream(1); await softCheckAsync( - queue, it>()..emitsThrough(it()..equals(42))); - checkThat(queue).emits().which(it()..equals(0)); + queue, it>()..emitsThrough(equals(42))); + checkThat(queue).emits().which(equals(0)); }); test('consumes events', () async { final queue = _countingStream(3); - await checkThat(queue).emitsThrough(it()..equals(1)); - await checkThat(queue).emits().which((it()..equals(2))); + await checkThat(queue).emitsThrough(equals(1)); + await checkThat(queue).emits().which((equals(2))); }); }); group('emitsInOrder', () { test('succeeds for happy case', () async { await checkThat(_countingStream(2)).inOrder([ - it()..emits().which(it()..equals(0)), - it()..emits().which((it()..equals(1))), + it()..emits().which(equals(0)), + it()..emits().which((equals(1))), it()..isDone(), ]); }); @@ -275,7 +275,7 @@ fake trace'''); }); test('nestes the report for deep failures', () async { await checkThat(_countingStream(2)).isRejectedByAsync( - it()..inOrder([it()..emits(), it()..emits().which(it()..equals(2))]), + it()..inOrder([it()..emits(), it()..emits().which(equals(2))]), actual: ['a stream'], which: [ 'satisfied 1 conditions then', @@ -298,21 +298,21 @@ fake trace'''); queue, it() ..inOrder([ - it()..emits().which(it()..equals(0)), - it()..emits().which(it()..equals(1)), - it()..emits().which(it()..equals(42)), + it()..emits().which(equals(0)), + it()..emits().which(equals(1)), + it()..emits().which(equals(42)), ])); await checkThat(queue).inOrder([ - it()..emits().which(it()..equals(0)), - it()..emits().which(it()..equals(1)), - it()..emits().which(it()..equals(2)), + it()..emits().which(equals(0)), + it()..emits().which(equals(1)), + it()..emits().which(equals(2)), it()..isDone(), ]); }); test('consumes events', () async { final queue = _countingStream(3); await checkThat(queue).inOrder([it()..emits(), it()..emits()]); - await checkThat(queue).emits().which(it()..equals(2)); + await checkThat(queue).emits().which(equals(2)); }); }); @@ -320,17 +320,17 @@ fake trace'''); test( 'succeeds for a stream that closes without emitting a matching value', () async { - await checkThat(_countingStream(5)).neverEmits(it()..equals(5)); + await checkThat(_countingStream(5)).neverEmits(equals(5)); }); test('fails for a stream that emits a matching value', () async { await checkThat(_countingStream(6)).isRejectedByAsync( - it()..neverEmits(it()..equals(5)), + it()..neverEmits(equals(5)), actual: ['a stream'], which: ['emitted <5>', 'following 5 other items'], ); }); test('can be described', () async { - await checkThat(it>()..neverEmits(it()..equals(42))) + await checkThat(it>()..neverEmits(equals(42))) .asyncDescription .which(it() ..deepEquals([ @@ -341,10 +341,10 @@ fake trace'''); test('uses a transaction', () async { final queue = _countingStream(2); await softCheckAsync>( - queue, it()..neverEmits(it()..equals(1))); + queue, it()..neverEmits(equals(1))); await checkThat(queue).inOrder([ - it()..emits().which(it()..equals(0)), - it()..emits().which(it()..equals(1)), + it()..emits().which(equals(0)), + it()..emits().which(equals(1)), it()..isDone(), ]); }); @@ -352,31 +352,27 @@ fake trace'''); group('mayEmit', () { test('succeeds for a stream that emits a matching value', () async { - await checkThat(_countingStream(1)).mayEmit(it()..equals(0)); + await checkThat(_countingStream(1)).mayEmit(equals(0)); }); test('succeeds for a stream that emits an error', () async { - await checkThat(_countingStream(1, errorAt: 0)) - .mayEmit(it()..equals(0)); + await checkThat(_countingStream(1, errorAt: 0)).mayEmit(equals(0)); }); test('succeeds for a stream that closes', () async { - await checkThat(_countingStream(0)).mayEmit(it()..equals(42)); + await checkThat(_countingStream(0)).mayEmit(equals(42)); }); test('consumes a matching event', () async { final queue = _countingStream(2); - await softCheckAsync>( - queue, it()..mayEmit(it()..equals(0))); - await checkThat(queue).emits().which(it()..equals(1)); + await softCheckAsync>(queue, it()..mayEmit(equals(0))); + await checkThat(queue).emits().which(equals(1)); }); test('does not consume a non-matching event', () async { final queue = _countingStream(2); - await softCheckAsync>( - queue, it()..mayEmit(it()..equals(1))); - await checkThat(queue).emits().which(it()..equals(0)); + await softCheckAsync>(queue, it()..mayEmit(equals(1))); + await checkThat(queue).emits().which(equals(0)); }); test('does not consume an error', () async { final queue = _countingStream(1, errorAt: 0); - await softCheckAsync>( - queue, it()..mayEmit(it()..equals(0))); + await softCheckAsync>(queue, it()..mayEmit(equals(0))); await checkThat(queue) .emitsError() .which(it()..has((e) => e.message, 'message').equals('Error at 1')); @@ -385,31 +381,31 @@ fake trace'''); group('mayEmitMultiple', () { test('succeeds for a stream that emits a matching value', () async { - await checkThat(_countingStream(1)).mayEmitMultiple(it()..equals(0)); + await checkThat(_countingStream(1)).mayEmitMultiple(equals(0)); }); test('succeeds for a stream that emits an error', () async { await checkThat(_countingStream(1, errorAt: 0)) - .mayEmitMultiple(it()..equals(0)); + .mayEmitMultiple(equals(0)); }); test('succeeds for a stream that closes', () async { - await checkThat(_countingStream(0)).mayEmitMultiple(it()..equals(42)); + await checkThat(_countingStream(0)).mayEmitMultiple(equals(42)); }); test('consumes matching events', () async { final queue = _countingStream(3); await softCheckAsync>( queue, it()..mayEmitMultiple(it()..isLessThan(2))); - await checkThat(queue).emits().which(it()..equals(2)); + await checkThat(queue).emits().which(equals(2)); }); test('consumes no events if no events match', () async { final queue = _countingStream(2); await softCheckAsync>( queue, it()..mayEmitMultiple(it()..isLessThan(0))); - await checkThat(queue).emits().which(it()..equals(0)); + await checkThat(queue).emits().which(equals(0)); }); test('does not consume an error', () async { final queue = _countingStream(1, errorAt: 0); await softCheckAsync>( - queue, it()..mayEmitMultiple(it()..equals(0))); + queue, it()..mayEmitMultiple(equals(0))); await checkThat(queue) .emitsError() .which(it()..has((e) => e.message, 'message').equals('Error at 1')); @@ -435,7 +431,7 @@ fake trace'''); test('uses a transaction', () async { final queue = _countingStream(1); await softCheckAsync>(queue, it()..isDone()); - await checkThat(queue).emits().which(it()..equals(0)); + await checkThat(queue).emits().which(equals(0)); }); test('can be described', () async { await checkThat(it>()..isDone()) @@ -447,8 +443,8 @@ fake trace'''); group('emitsAnyOf', () { test('succeeds for a stream that matches one condition', () async { await checkThat(_countingStream(1)).anyOf([ - it()..emits().which(it()..equals(42)), - it()..emits().which((it()..equals(0))) + it()..emits().which(equals(42)), + it()..emits().which((equals(0))) ]); }); test('fails for a stream that matches no conditions', () async { @@ -456,7 +452,7 @@ fake trace'''); it() ..anyOf([ it()..emits(), - it()..emitsThrough(it()..equals(1)), + it()..emitsThrough(equals(1)), ]), actual: [ 'a stream' @@ -473,8 +469,8 @@ fake trace'''); await checkThat(_countingStream(1)).isRejectedByAsync( it() ..anyOf([ - it()..emits().which(it()..equals(42)), - it()..emitsThrough(it()..equals(10)), + it()..emits().which(equals(42)), + it()..emitsThrough(equals(10)), ]), actual: [ 'a stream' @@ -501,25 +497,23 @@ fake trace'''); queue, it() ..anyOf([ - it()..emits().which(it()..equals(10)), - it()..emitsThrough(it()..equals(42)), + it()..emits().which(equals(10)), + it()..emitsThrough(equals(42)), ])); - await checkThat(queue).emits().which(it()..equals(0)); + await checkThat(queue).emits().which(equals(0)); }); test('consumes events', () async { final queue = _countingStream(3); - await checkThat(queue).anyOf([ - it()..emits().which(it()..equals(1)), - it()..emitsThrough(it()..equals(1)) - ]); - await checkThat(queue).emits().which(it()..equals(2)); + await checkThat(queue).anyOf( + [it()..emits().which(equals(1)), it()..emitsThrough(equals(1))]); + await checkThat(queue).emits().which(equals(2)); }); }); }); group('ChainAsync', () { test('which', () async { - await checkThat(_futureSuccess()).completes().which(it()..equals(42)); + await checkThat(_futureSuccess()).completes().which(equals(42)); }); }); diff --git a/pkgs/checks/test/extensions/core_test.dart b/pkgs/checks/test/extensions/core_test.dart index 0d037c7d0..411562eea 100644 --- a/pkgs/checks/test/extensions/core_test.dart +++ b/pkgs/checks/test/extensions/core_test.dart @@ -68,7 +68,7 @@ void main() { test('equals', () { checkThat(1).equals(1); - checkThat(1).isRejectedBy(it()..equals(2), which: ['are not equal']); + checkThat(1).isRejectedBy(equals(2), which: ['are not equal']); }); test('identical', () { checkThat(1).identicalTo(1); diff --git a/pkgs/checks/test/extensions/iterable_test.dart b/pkgs/checks/test/extensions/iterable_test.dart index 052a6c27c..2081ecb0a 100644 --- a/pkgs/checks/test/extensions/iterable_test.dart +++ b/pkgs/checks/test/extensions/iterable_test.dart @@ -41,8 +41,8 @@ void main() { .isRejectedBy(it()..contains(2), which: ['does not contain <2>']); }); test('any', () { - checkThat(_testIterable).any(it()..equals(1)); - checkThat(_testIterable).isRejectedBy(it()..any(it()..equals(2)), + checkThat(_testIterable).any(equals(1)); + checkThat(_testIterable).isRejectedBy(it()..any(equals(2)), which: ['Contains no matching element']); }); @@ -75,7 +75,7 @@ void main() { checkThat(it()..containsInOrder([1, 2, 3])) .description .deepEquals([' contains, in order: [1, 2, 3]']); - checkThat(it()..containsInOrder([1, it()..equals(2)])) + checkThat(it()..containsInOrder([1, equals(2)])) .description .deepEquals([ ' contains, in order: [1,', @@ -125,14 +125,14 @@ void main() { group('unorderedMatches', () { test('success for happy case', () { checkThat(_testIterable).unorderedMatches( - _testIterable.toList().reversed.map((i) => it()..equals(i))); + _testIterable.toList().reversed.map((i) => equals(i))); }); test('reports unmatched elements', () { checkThat(_testIterable).isRejectedBy( it() - ..unorderedMatches(_testIterable - .followedBy([42, 100]).map((i) => it()..equals(i))), + ..unorderedMatches( + _testIterable.followedBy([42, 100]).map((i) => equals(i))), which: [ 'has no element matching the condition at index 2:', ' equals <42>', @@ -142,7 +142,7 @@ void main() { test('reports unexpected elements', () { checkThat(_testIterable.followedBy([42, 100])).isRejectedBy( - it()..unorderedMatches(_testIterable.map((i) => it()..equals(i))), + it()..unorderedMatches(_testIterable.map((i) => equals(i))), which: [ 'has an unmatched element at index 2: <42>', 'and 1 other unmatched elements' diff --git a/pkgs/checks/test/extensions/map_test.dart b/pkgs/checks/test/extensions/map_test.dart index 83a5f19af..0aaef0de6 100644 --- a/pkgs/checks/test/extensions/map_test.dart +++ b/pkgs/checks/test/extensions/map_test.dart @@ -52,9 +52,9 @@ void main() { ); }); test('containsKeyThat', () { - checkThat(_testMap).containsKeyThat(it()..equals('a')); + checkThat(_testMap).containsKeyThat(equals('a')); checkThat(_testMap).isRejectedBy( - it()..containsKeyThat(it()..equals('c')), + it()..containsKeyThat(equals('c')), which: ['Contains no matching key'], ); }); @@ -66,9 +66,9 @@ void main() { ); }); test('containsValueThat', () { - checkThat(_testMap).containsValueThat(it()..equals(1)); + checkThat(_testMap).containsValueThat(equals(1)); checkThat(_testMap).isRejectedBy( - it()..containsValueThat(it()..equals(3)), + it()..containsValueThat(equals(3)), which: ['Contains no matching value'], ); }); diff --git a/pkgs/checks/test/extensions/string_test.dart b/pkgs/checks/test/extensions/string_test.dart index fadbef534..7327f9c1f 100644 --- a/pkgs/checks/test/extensions/string_test.dart +++ b/pkgs/checks/test/extensions/string_test.dart @@ -76,38 +76,36 @@ void main() { checkThat('').equals(''); }); test('reports extra characters for long string', () { - checkThat('foobar').isRejectedBy(it()..equals('foo'), + checkThat('foobar').isRejectedBy(equals('foo'), which: ['is too long with unexpected trailing characters:', 'bar']); }); test('reports extra characters for long string against empty', () { checkThat('foo') - .isRejectedBy(it()..equals(''), which: ['is not the empty string']); + .isRejectedBy(equals(''), which: ['is not the empty string']); }); test('reports truncated extra characters for very long string', () { - checkThat('foobar baz more stuff').isRejectedBy(it()..equals('foo'), - which: [ - 'is too long with unexpected trailing characters:', - 'bar baz mo ...' - ]); + checkThat('foobar baz more stuff').isRejectedBy(equals('foo'), which: [ + 'is too long with unexpected trailing characters:', + 'bar baz mo ...' + ]); }); test('reports missing characters for short string', () { - checkThat('foo').isRejectedBy(it()..equals('foobar'), + checkThat('foo').isRejectedBy(equals('foobar'), which: ['is too short with missing trailing characters:', 'bar']); }); test('reports missing characters for empty string', () { - checkThat('').isRejectedBy(it()..equals('foo bar baz'), + checkThat('').isRejectedBy(equals('foo bar baz'), actual: ['an empty string'], which: ['is missing all expected characters:', 'foo bar ba ...']); }); test('reports truncated missing characters for very short string', () { - checkThat('foo').isRejectedBy(it()..equals('foobar baz more stuff'), - which: [ - 'is too short with missing trailing characters:', - 'bar baz mo ...' - ]); + checkThat('foo').isRejectedBy(equals('foobar baz more stuff'), which: [ + 'is too short with missing trailing characters:', + 'bar baz mo ...' + ]); }); test('reports index of different character', () { - checkThat('hit').isRejectedBy(it()..equals('hat'), which: [ + checkThat('hit').isRejectedBy(equals('hat'), which: [ 'differs at offset 1:', 'hat', 'hit', @@ -116,14 +114,13 @@ void main() { }); test('reports truncated index of different character in large string', () { - checkThat('blah blah blah hit blah blah blah').isRejectedBy( - it()..equals('blah blah blah hat blah blah blah'), - which: [ - 'differs at offset 16:', - '... lah blah hat blah bl ...', - '... lah blah hit blah bl ...', - ' ^', - ]); + checkThat('blah blah blah hit blah blah blah') + .isRejectedBy(equals('blah blah blah hat blah blah blah'), which: [ + 'differs at offset 16:', + '... lah blah hat blah bl ...', + '... lah blah hit blah bl ...', + ' ^', + ]); }); }); From 1d964200879d067ca22e64bad45bea755f190119 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 2 Feb 2023 04:27:44 +0000 Subject: [PATCH 2/2] Specialize top level equals for Strings This demonstrates a risk of this method - if other packages add an extension which specialized `equals` for other Subject types, they won't get picked up by `equals()` but would get picked up by `it()..equals()`. --- pkgs/checks/lib/src/extensions/core.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/checks/lib/src/extensions/core.dart b/pkgs/checks/lib/src/extensions/core.dart index 91a973015..1fc34be9d 100644 --- a/pkgs/checks/lib/src/extensions/core.dart +++ b/pkgs/checks/lib/src/extensions/core.dart @@ -101,7 +101,10 @@ extension CoreChecks on Subject { /// by operator `==`. /// /// This is a shortcut for `it()..equals(expected)`. -Condition equals(T expected) => it()..equals(expected); +Condition equals(T expected) => T == String + // String specializes `equals` with a better failure + ? ((it()..equals(expected as String)) as Condition) + : (it()..equals(expected)); extension BoolChecks on Subject { void isTrue() {