From 6ead2a4e8b1256547d4dff52031c1ec6fa266669 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Wed, 2 Aug 2023 20:31:53 +0200 Subject: [PATCH 1/3] feat(sequence): Add support for nested traversable --- src/Expectation.php | 35 ++++++++++++++---------------- tests/.snapshots/success.txt | 6 +++-- tests/Features/Expect/sequence.php | 33 ++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index aebbdd68..32ceb896 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -6,6 +6,8 @@ namespace Pest; use BadMethodCallException; use Closure; +use InvalidArgumentException; +use OutOfRangeException; use Pest\Arch\Contracts\ArchExpectation; use Pest\Arch\Expectations\Targeted; use Pest\Arch\Expectations\ToBeUsedIn; @@ -28,7 +30,6 @@ use Pest\Expectations\OppositeExpectation; use Pest\Matchers\Any; use Pest\Support\ExpectationPipeline; use PHPUnit\Architecture\Elements\ObjectDescription; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\ExpectationFailedException; /** @@ -185,30 +186,26 @@ final class Expectation throw new BadMethodCallException('Expectation value is not iterable.'); } - $value = is_array($this->value) ? $this->value : iterator_to_array($this->value); - $keys = array_keys($value); - $values = array_values($value); - $callbacksCount = count($callbacks); - - $index = 0; - - while (count($callbacks) < count($values)) { - $callbacks[] = $callbacks[$index]; - $index = $index < count($values) - 1 ? $index + 1 : 0; + if (empty($callbacks)) { + throw new InvalidArgumentException('No sequence expectations defined.'); } - if ($callbacksCount > count($values)) { - Assert::assertLessThanOrEqual(count($value), count($callbacks)); - } + $index = $valuesCount = 0; - foreach ($values as $key => $item) { - if ($callbacks[$key] instanceof Closure) { - call_user_func($callbacks[$key], new self($item), new self($keys[$key])); + foreach ($this->value as $key => $value) { + $valuesCount++; - continue; + if ($callbacks[$index] instanceof Closure) { + $callbacks[$index](new self($value), new self($key)); + } else { + (new self($value))->toEqual($callbacks[$index]); } - (new self($item))->toEqual($callbacks[$key]); + $index = isset($callbacks[$index + 1]) ? $index + 1 : 0; + } + + if (count($callbacks) > $valuesCount) { + throw new OutOfRangeException('Sequence expectations are more than the iterable items.'); } return $this; diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index f9d7233c..4e6ae2c3 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -297,12 +297,14 @@ PASS Tests\Features\Expect\sequence ✓ an exception is thrown if the the type is not iterable + ✓ an exception is thrown if there are no expectations ✓ allows for sequences of checks to be run on iterable data ✓ loops back to the start if it runs out of sequence items - ✓ fails if the number of iterable items is greater than the number of expectations + ✓ fails if the number of iterable items is less than the number of expectations ✓ it works with associative arrays ✓ it can be passed non-callable values ✓ it can be passed a mixture of value types + ✓ it works with traversables PASS Tests\Features\Expect\toBe ✓ strict comparisons @@ -1194,4 +1196,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 844 passed (1947 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 845 passed (1965 assertions) \ No newline at end of file diff --git a/tests/Features/Expect/sequence.php b/tests/Features/Expect/sequence.php index e145f743..2c19e206 100644 --- a/tests/Features/Expect/sequence.php +++ b/tests/Features/Expect/sequence.php @@ -1,11 +1,13 @@ each->sequence(); })->throws(BadMethodCallException::class, 'Expectation value is not iterable.'); +test('an exception is thrown if there are no expectations', function () { + expect(['Foobar'])->sequence(); +})->throws(InvalidArgumentException::class, 'No sequence expectations defined.'); + test('allows for sequences of checks to be run on iterable data', function () { expect([1, 2, 3]) ->sequence( @@ -40,7 +42,7 @@ test('loops back to the start if it runs out of sequence items', function () { expect(static::getCount())->toBe(16); }); -test('fails if the number of iterable items is greater than the number of expectations', function () { +test('fails if the number of iterable items is less than the number of expectations', function () { expect([1, 2]) ->sequence( function ($expectation) { @@ -53,7 +55,7 @@ test('fails if the number of iterable items is greater than the number of expect $expectation->toBeInt()->toEqual(3); }, ); -})->throws(ExpectationFailedException::class); +})->throws(OutOfRangeException::class, 'Sequence expectations are more than the iterable items.'); test('it works with associative arrays', function () { expect(['foo' => 'bar', 'baz' => 'boom']) @@ -86,3 +88,26 @@ test('it can be passed a mixture of value types', function () { expect(static::getCount())->toBe(4); }); + +test('it works with traversables', function () { + $generator = (function () { + yield 'one' => (fn () => yield from [1, 2, 3])(); + yield 'two' => (fn () => yield from [4, 5, 6])(); + yield 'three' => (fn () => yield from [7, 8, 9])(); + })(); + + expect($generator)->sequence( + fn ($value, $key) => $key->toBe('one') + ->and($value) + ->toBeInstanceOf(Generator::class) + ->sequence(1, 2, 3), + fn ($value, $key) => $key->toBe('two') + ->and($value) + ->toBeInstanceOf(Generator::class) + ->sequence(4, 5, 6), + fn ($value, $key) => $key->toBe('three') + ->and($value) + ->toBeInstanceOf(Generator::class) + ->sequence(7, 8, 9), + ); +}); From fb75b712d34d5dbc3c9ea5419ffc8ddf9ba168bc Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Wed, 2 Aug 2023 20:49:05 +0200 Subject: [PATCH 2/3] chore: update snapshot --- tests/.snapshots/success.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 4e6ae2c3..ee650c64 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1196,4 +1196,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 845 passed (1965 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 19 skipped, 846 passed (1968 assertions) \ No newline at end of file From d94a6580f5faba7cb57895ac8b225fd32c5ac09e Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Wed, 2 Aug 2023 20:49:27 +0200 Subject: [PATCH 3/3] fix: type check --- src/Expectation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Expectation.php b/src/Expectation.php index 32ceb896..61864092 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -186,7 +186,7 @@ final class Expectation throw new BadMethodCallException('Expectation value is not iterable.'); } - if (empty($callbacks)) { + if (count($callbacks) == 0) { throw new InvalidArgumentException('No sequence expectations defined.'); }