From 108fe451645746d3725d1dd5eb01bbefa02ca933 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 10 Jan 2022 17:20:12 +0000 Subject: [PATCH 01/11] chore: uses collision `v5.11.0` --- composer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4be59682..7251a708 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { "php": "^8.0", - "nunomaduro/collision": "^5.10.0|^6.0", + "nunomaduro/collision": "^5.11.0|^6.0.0", "pestphp/pest-plugin": "^1.0.0", "phpunit/phpunit": "10.0.x-dev" }, @@ -54,7 +54,10 @@ "prefer-stable": true, "config": { "sort-packages": true, - "preferred-install": "dist" + "preferred-install": "dist", + "allow-plugins": { + "pestphp/pest-plugin": true + } }, "bin": [ "bin/pest" From b3d3b4485d9a008676c154f9399c5c13ac183804 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 16 Jan 2022 22:32:20 +1000 Subject: [PATCH 02/11] Do not nest expectations --- src/Expectation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Expectation.php b/src/Expectation.php index f98638f7..8dc287a0 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -56,7 +56,7 @@ final class Expectation */ public function and(mixed $value): Expectation { - return new self($value); + return $value instanceof static ? $value : new self($value); } /** From d24091d224d52b8178be7ec5fc47d72def61f340 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 16 Jan 2022 22:32:38 +1000 Subject: [PATCH 03/11] Fix return docblock --- src/Support/HigherOrderCallables.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php index c6533929..9790d2cb 100644 --- a/src/Support/HigherOrderCallables.php +++ b/src/Support/HigherOrderCallables.php @@ -44,7 +44,7 @@ final class HigherOrderCallables * * @param callable|TValue $value * - * @return Expectation + * @return Expectation<(callable(): mixed)|TValue> */ public function and(mixed $value) { From 30b1f6cd0a5ef5f07251fc97a2d02b55fb69cb17 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 16 Jan 2022 22:32:55 +1000 Subject: [PATCH 04/11] Add test for and() --- .../Features/Expect/HigherOrder/methodsAndProperties.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Features/Expect/HigherOrder/methodsAndProperties.php b/tests/Features/Expect/HigherOrder/methodsAndProperties.php index b98c9f16..c9ef45aa 100644 --- a/tests/Features/Expect/HigherOrder/methodsAndProperties.php +++ b/tests/Features/Expect/HigherOrder/methodsAndProperties.php @@ -48,6 +48,15 @@ it('can start a new higher order expectation using the and syntax in higher orde ->toBeArray() ->foo->toEqual('bar'); +it('can start a new higher order expectation using the and syntax without nesting expectations', function () { + expect(new HasMethodsAndProperties()) + ->toBeInstanceOf(HasMethodsAndProperties::class) + ->meta + ->sequence( + function ($value, $key) { $value->toBeArray()->and($key)->toBe('foo'); }, + ); +}); + class HasMethodsAndProperties { public $name = 'Has Methods and Properties'; From b99c4d611bc87df7e978876124e8f180c6a8ea3f Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Thu, 20 Jan 2022 10:21:54 +0000 Subject: [PATCH 05/11] test --- tests/Features/Expect/each.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Features/Expect/each.php b/tests/Features/Expect/each.php index 0f26a974..ad61f578 100644 --- a/tests/Features/Expect/each.php +++ b/tests/Features/Expect/each.php @@ -79,11 +79,13 @@ it('can add expectations via "and"', function () { }); it('accepts callables', function () { - expect([1, 2, 3])->each(function ($number) { + expect([1, 2, 3])->each(function ($number, $key) { expect($number)->toBeInstanceOf(Expectation::class); expect($number->value)->toBeInt(); $number->toBeInt->not->toBeString; + + expect($key)->toBeInt(); }); - expect(static::getCount())->toBe(12); + expect(static::getCount())->toBe(15); }); From 635a71ce666518aa914170c5ca93da9dfaf08bd6 Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Thu, 20 Jan 2022 10:21:57 +0000 Subject: [PATCH 06/11] feature --- src/Expectation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 8dc287a0..300820c1 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -125,8 +125,8 @@ final class Expectation } if (is_callable($callback)) { - foreach ($this->value as $item) { - $callback(new self($item)); + foreach ($this->value as $key => $item) { + $callback(new self($item), $key); } } From 12b48a6cf698a76b2d54407b8e348f78a245de93 Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Thu, 20 Jan 2022 10:34:32 +0000 Subject: [PATCH 07/11] move to new test --- tests/Features/Expect/each.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/Features/Expect/each.php b/tests/Features/Expect/each.php index ad61f578..63b3d60c 100644 --- a/tests/Features/Expect/each.php +++ b/tests/Features/Expect/each.php @@ -79,13 +79,19 @@ it('can add expectations via "and"', function () { }); it('accepts callables', function () { - expect([1, 2, 3])->each(function ($number, $key) { + expect([1, 2, 3])->each(function ($number) { expect($number)->toBeInstanceOf(Expectation::class); expect($number->value)->toBeInt(); $number->toBeInt->not->toBeString; + }); + expect(static::getCount())->toBe(12); +}); + +it('passes the key of the current item to callables', function () { + expect([1, 2, 3])->each(function ($number, $key) { expect($key)->toBeInt(); }); - expect(static::getCount())->toBe(15); + expect(static::getCount())->toBe(3); }); From ead2dfd0a9e244e6b6938757122272b630abd7a3 Mon Sep 17 00:00:00 2001 From: Luke Downing Date: Sun, 23 Jan 2022 23:10:14 +0000 Subject: [PATCH 08/11] Makes `json` expectation usable in Higher Order Tests --- src/Expectation.php | 7 +++++-- src/Expectations/HigherOrderExpectation.php | 10 ++++++++++ tests/Features/Expect/HigherOrder/methods.php | 12 ++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 300820c1..7703ebb8 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -62,7 +62,7 @@ final class Expectation /** * Creates a new expectation with the decoded JSON value. * - * @return self + * @return self|bool> */ public function json(): Expectation { @@ -70,7 +70,10 @@ final class Expectation InvalidExpectationValue::expected('string'); } - return $this->toBeJson()->and(json_decode($this->value, true)); + /** @var array|bool $value */ + $value = json_decode($this->value, true); + + return $this->toBeJson()->and($value); } /** diff --git a/src/Expectations/HigherOrderExpectation.php b/src/Expectations/HigherOrderExpectation.php index 83c43643..1879e167 100644 --- a/src/Expectations/HigherOrderExpectation.php +++ b/src/Expectations/HigherOrderExpectation.php @@ -79,6 +79,16 @@ final class HigherOrderExpectation return $this->expect($value); } + /** + * Creates a new expectation with the decoded JSON value. + * + * @return self|bool> + */ + public function json(): self + { + return new self($this->original, $this->expectation->json()->value); + } + /** * Dynamically calls methods on the class with the given arguments. * diff --git a/tests/Features/Expect/HigherOrder/methods.php b/tests/Features/Expect/HigherOrder/methods.php index d6da636b..da610cf9 100644 --- a/tests/Features/Expect/HigherOrder/methods.php +++ b/tests/Features/Expect/HigherOrder/methods.php @@ -74,8 +74,20 @@ it('works with higher order tests') ->name()->toEqual('Has Methods') ->books()->each->toBeArray; +it('works consistently with the json expectation method', function () { + expect(new HasMethods()) + ->jsonString()->json()->id->toBe(1) + ->jsonString()->json()->name->toBe('Has Methods')->toBeString() + ->jsonString()->json()->quantity->toBe(20)->toBeInt(); +}); + class HasMethods { + public function jsonString(): string + { + return '{ "id": 1, "name": "Has Methods", "quantity": 20 }'; + } + public function name() { return 'Has Methods'; From b99f65d93628148525c783d789402433ee3c500d Mon Sep 17 00:00:00 2001 From: Luke Downing Date: Sun, 23 Jan 2022 23:18:55 +0000 Subject: [PATCH 09/11] Renames `tap` to `defer`. --- src/Support/HigherOrderCallables.php | 4 ++-- tests/Features/HigherOrderTests.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php index 9790d2cb..1ccf1f9f 100644 --- a/src/Support/HigherOrderCallables.php +++ b/src/Support/HigherOrderCallables.php @@ -52,9 +52,9 @@ final class HigherOrderCallables } /** - * Tap into the test case to perform an action and return the test case. + * Execute the given callable after the test has executed the setup method. */ - public function tap(callable $callable): object + public function defer(callable $callable): object { Reflection::bindCallableWithData($callable); diff --git a/tests/Features/HigherOrderTests.php b/tests/Features/HigherOrderTests.php index e2ff0686..7aafbaa5 100644 --- a/tests/Features/HigherOrderTests.php +++ b/tests/Features/HigherOrderTests.php @@ -21,9 +21,9 @@ it('resolves expect callables correctly') test('does not treat method names as callables') ->expect('it')->toBeString(); -it('can tap into the test') +it('can defer a method until after test setup') ->expect('foo')->toBeString() - ->tap(function () { expect($this)->toBeInstanceOf(TestCase::class); }) + ->defer(function () { expect($this)->toBeInstanceOf(TestCase::class); }) ->toBe('foo') ->and('hello world')->toBeString(); @@ -32,15 +32,15 @@ it('can pass datasets into the expect callables') ->expect(function (...$numbers) { return $numbers; })->toBe([1, 2, 3]) ->and(function (...$numbers) { return $numbers; })->toBe([1, 2, 3]); -it('can pass datasets into the tap callable') +it('can pass datasets into the defer callable') ->with([[1, 2, 3]]) - ->tap(function (...$numbers) { expect($numbers)->toBe([1, 2, 3]); }); + ->defer(function (...$numbers) { expect($numbers)->toBe([1, 2, 3]); }); it('can pass shared datasets into callables') ->with('numbers.closure.wrapped') ->expect(function ($value) { return $value; }) ->and(function ($value) { return $value; }) - ->tap(function ($value) { expect($value)->toBeInt(); }) + ->defer(function ($value) { expect($value)->toBeInt(); }) ->toBeInt(); afterEach()->assertTrue(true); From e91c85496f078e50b61873ab5915934655b1faa6 Mon Sep 17 00:00:00 2001 From: Luke Downing Date: Sun, 23 Jan 2022 23:37:03 +0000 Subject: [PATCH 10/11] Adds support for `scoped` in HigherOrderExpectations. --- src/Expectations/HigherOrderExpectation.php | 16 ++++++++++++++++ tests/Features/Expect/HigherOrder/methods.php | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Expectations/HigherOrderExpectation.php b/src/Expectations/HigherOrderExpectation.php index 83c43643..7fb0300e 100644 --- a/src/Expectations/HigherOrderExpectation.php +++ b/src/Expectations/HigherOrderExpectation.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Pest\Expectations; +use Closure; use Pest\Concerns\Retrievable; use Pest\Expectation; @@ -79,6 +80,21 @@ final class HigherOrderExpectation return $this->expect($value); } + /** + * Scope an expectation callback to the current value in + * the HigherOrderExpectation chain. + * + * @param Closure(Expectation): void $expectation + * + * @return HigherOrderExpectation + */ + public function scoped(Closure $expectation): self + { + $expectation->__invoke($this->expectation); + + return new self($this->original, $this->original->value); + } + /** * Dynamically calls methods on the class with the given arguments. * diff --git a/tests/Features/Expect/HigherOrder/methods.php b/tests/Features/Expect/HigherOrder/methods.php index d6da636b..ad6cf952 100644 --- a/tests/Features/Expect/HigherOrder/methods.php +++ b/tests/Features/Expect/HigherOrder/methods.php @@ -74,6 +74,23 @@ it('works with higher order tests') ->name()->toEqual('Has Methods') ->books()->each->toBeArray; +it('can use the scoped method to lock into the given level for expectations', function () { + expect(new HasMethods()) + ->attributes()->scoped(fn ($attributes) => $attributes + ->name->toBe('Has Methods') + ->quantity->toBe(20) + ) + ->name()->toBeString()->toBe('Has Methods') + ->newInstance()->newInstance()->scoped(fn ($instance) => $instance + ->name()->toBe('Has Methods') + ->quantity()->toBe(20) + ->attributes()->scoped(fn ($attributes) => $attributes + ->name->toBe('Has Methods') + ->quantity->toBe(20) + ) + ); +}); + class HasMethods { public function name() From 6f9ebe04b04f453fe2d028032770e005bc5343be Mon Sep 17 00:00:00 2001 From: Fabio Ivona Date: Sat, 5 Feb 2022 11:36:02 +0100 Subject: [PATCH 11/11] phpstan fix --- src/Expectation.php | 1 + src/Support/Backtrace.php | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 7703ebb8..191d762e 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -151,6 +151,7 @@ final class Expectation throw new BadMethodCallException('Expectation value is not iterable.'); } + //@phpstan-ignore-next-line $value = is_array($this->value) ? $this->value : iterator_to_array($this->value); $keys = array_keys($value); $values = array_values($value); diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index 09d9e438..56ac4fc9 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -26,6 +26,8 @@ final class Backtrace $current = null; foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { + assert(array_key_exists(self::FILE, $trace)); + if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) { break; } @@ -45,7 +47,11 @@ final class Backtrace */ public static function file(): string { - return debug_backtrace(self::BACKTRACE_OPTIONS)[1][self::FILE]; + $trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1]; + + assert(array_key_exists(self::FILE, $trace)); + + return $trace[self::FILE]; } /** @@ -53,7 +59,11 @@ final class Backtrace */ public static function dirname(): string { - return dirname(debug_backtrace(self::BACKTRACE_OPTIONS)[1][self::FILE]); + $trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1]; + + assert(array_key_exists(self::FILE, $trace)); + + return dirname($trace[self::FILE]); } /** @@ -61,6 +71,10 @@ final class Backtrace */ public static function line(): int { - return debug_backtrace(self::BACKTRACE_OPTIONS)[1]['line']; + $trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1]; + + assert(array_key_exists('line', $trace)); + + return $trace['line']; } }