From 0d5572c21dfe028db4fa38834bffd1a30a95347e Mon Sep 17 00:00:00 2001 From: Sean Fisher Date: Fri, 27 Sep 2024 11:22:25 -0400 Subject: [PATCH] Add andReturnBoolean and andReturn with callable support (#590) * Allow callable and boolean return types for action/filter assertions * CHANGELOG --- CHANGELOG.md | 2 + .../testing/expectation/class-expectation.php | 60 +++++++++++-------- .../Concerns/InteractsWithHooksTest.php | 18 ++++++ 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcd6ef8f..099c52e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added dynamic creation of post type/taxonomy factories. - Added `Reset_Server` trait to reset the server between tests. - Add `with_https()` to control if the request being tested is over HTTPS. +- Add `andReturnBoolean()` and `andReturn( fn ( $value ) => ... )` support to + action/filter expectations - Add cached HTTP response support using the `cache()` method. ### Changed diff --git a/src/mantle/testing/expectation/class-expectation.php b/src/mantle/testing/expectation/class-expectation.php index 59a6b5ec..d95d90e7 100644 --- a/src/mantle/testing/expectation/class-expectation.php +++ b/src/mantle/testing/expectation/class-expectation.php @@ -16,20 +16,6 @@ * Expectation for an action to be added/applied. */ class Expectation { - /** - * Action to expect. - * - * @var string - */ - protected $action; - - /** - * Hook to compare. - * - * @var string - */ - protected $hook; - /** * Arguments for the hook. * @@ -39,10 +25,8 @@ class Expectation { /** * Number of times for the hook to execute. - * - * @var int|null */ - protected $times; + protected int|null $times = null; /** * Return value comparison callback. @@ -72,10 +56,7 @@ class Expectation { * @param string $hook Hook to listen to. * @param mixed $args Arguments for the hook. */ - public function __construct( string $action, string $hook, $args = null ) { - $this->action = $action; - $this->hook = $hook; - + public function __construct( protected readonly string $action, protected readonly string $hook, $args = null ) { if ( ! empty( $args ) ) { $this->args = $args; } @@ -92,7 +73,7 @@ protected function setup_applied_hooks() { add_action( // @phpstan-ignore-line Action callback $this->hook, [ $this, 'record_start' ], - -1, + PHP_INT_MIN, 99 ); @@ -112,6 +93,7 @@ protected function setup_applied_hooks() { */ public function record_start( ...$args ) { $this->record_start[] = $args; + return array_shift( $args ); } @@ -123,6 +105,7 @@ public function record_start( ...$args ) { */ public function record_stop( ...$args ) { $this->record_stop[] = $args; + return array_shift( $args ); } @@ -179,7 +162,7 @@ public function validate(): void { } // Remove the actions for the hook. - remove_action( $this->hook, [ $this, 'record_start' ], -1 ); + remove_action( $this->hook, [ $this, 'record_start' ], PHP_INT_MIN ); remove_action( $this->hook, [ $this, 'record_stop' ], PHP_INT_MAX ); } @@ -199,6 +182,7 @@ public function validate(): void { */ public function never() { $this->times = 0; + return $this; } @@ -209,6 +193,7 @@ public function never() { */ public function once() { $this->times = 1; + return $this; } @@ -219,6 +204,7 @@ public function once() { */ public function twice() { $this->times = 2; + return $this; } @@ -229,6 +215,7 @@ public function twice() { */ public function times( int $times ): static { $this->times = $times; + return $this; } @@ -239,6 +226,7 @@ public function times( int $times ): static { */ public function with( ...$args ): static { $this->args = $args; + return $this; } @@ -247,17 +235,30 @@ public function with( ...$args ): static { */ public function withAnyArgs(): static { $this->args = null; + return $this; } /** * Specify that the filter returns a specific value. * - * @param mixed $value Return value. + * @param mixed ...$values Values to return. */ - public function andReturn( mixed $value ): static { + public function andReturn( mixed ...$values ): static { return $this->returnComparison( - fn ( $return_value ) => $return_value === $value + function ( $value ) use ( $values ) { + foreach ( $values as $expected ) { + if ( is_callable( $expected ) ) { + return (bool) $expected( $value ); + } + + if ( $value === $expected ) { + return true; + } + } + + return false; + } ); } @@ -296,6 +297,13 @@ public function andReturnFalsy(): static { return $this->returnComparison( fn ( $value ) => ! $value ); } + /** + * Specify that the filter returns a boolean value. + */ + public function andReturnBoolean(): static { + return $this->andReturn( true, false ); + } + /** * Specify that the filter returns an empty value. */ diff --git a/tests/Testing/Concerns/InteractsWithHooksTest.php b/tests/Testing/Concerns/InteractsWithHooksTest.php index 8f1428a0..a9873638 100644 --- a/tests/Testing/Concerns/InteractsWithHooksTest.php +++ b/tests/Testing/Concerns/InteractsWithHooksTest.php @@ -53,6 +53,8 @@ public function test_hook_added_declaration() { public function test_hook_return_boolean() { $this->expectApplied( 'true_hook_to_add' )->once()->andReturnTrue(); $this->expectApplied( 'false_hook_to_add' )->once()->andReturnFalse(); + $this->expectApplied( 'true_hook_to_add' )->once()->andReturnBoolean(); + $this->expectApplied( 'false_hook_to_add' )->once()->andReturnBoolean(); add_filter( 'true_hook_to_add', '__return_true' ); add_filter( 'false_hook_to_add', '__return_false' ); @@ -119,6 +121,22 @@ public function test_hook_applied_event() { $this->assertHookApplied( Example_Event::class, 1 ); } + + public function test_hook_returns_callback() { + $passed_value = null; + + $this->expectApplied( 'callback_hook_to_add' )->once()->andReturn( + function ( $value ) use ( &$passed_value ) { + $passed_value = $value; + + return 'filtered-value' === $value; + } + ); + + add_filter( 'callback_hook_to_add', fn () => 'filtered-value' ); + + apply_filters( 'callback_hook_to_add', 'passed-value' ); + } } class Example_Event {}