From 7eb5478c42db969779eebcde4630010d4bb1a2f3 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Sun, 28 Mar 2021 01:54:43 -0500 Subject: [PATCH 01/14] test: add tests for shared/global before each hooks --- tests/Hooks/BeforeEachTest.php | 11 +++++++++++ tests/Pest.php | 3 +++ 2 files changed, 14 insertions(+) create mode 100644 tests/Hooks/BeforeEachTest.php diff --git a/tests/Hooks/BeforeEachTest.php b/tests/Hooks/BeforeEachTest.php new file mode 100644 index 00000000..199d6162 --- /dev/null +++ b/tests/Hooks/BeforeEachTest.php @@ -0,0 +1,11 @@ +baz)->toBe(1); // set from Pest.php global/shared hook + + $this->baz = 2; +}); + +test('global before each', function (): void { + expect($this->baz)->toBe(2); +}); diff --git a/tests/Pest.php b/tests/Pest.php index 2090100f..e2d51397 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,3 +1,6 @@ group('integration')->in('Visual'); +uses()->beforeEach(function (): void { + $this->baz = 1; +})->in('Hooks'); From 99500d0caeedbe13c70bc88e44d29536f19f872d Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Sun, 28 Mar 2021 02:03:31 -0500 Subject: [PATCH 02/14] feat: add shared/global before each hook --- src/Concerns/TestCase.php | 13 +++++++++++++ src/PendingObjects/UsesCall.php | 25 ++++++++++++++++++++++++- src/Repositories/TestRepository.php | 21 ++++++++++++--------- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/Concerns/TestCase.php b/src/Concerns/TestCase.php index 9581ae57..cc6a5f8d 100644 --- a/src/Concerns/TestCase.php +++ b/src/Concerns/TestCase.php @@ -55,6 +55,15 @@ trait TestCase $this->setGroups($groups); } + /** + * Add a shared/"global" before each test hook that will execute **before** + * the test defined `beforeEach` hook. + */ + public function addBeforeEach(?Closure $hook): void + { + $this->beforeEach = $hook; + } + /** * Add dependencies to the test case and map them to instances of ExecutionOrderDependency. */ @@ -121,6 +130,10 @@ trait TestCase parent::setUp(); + if ($this->beforeEach instanceof Closure) { + $this->__callClosure($this->beforeEach, func_get_args()); + } + $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); $this->__callClosure($beforeEach, func_get_args()); diff --git a/src/PendingObjects/UsesCall.php b/src/PendingObjects/UsesCall.php index f9418fd5..d0b33b38 100644 --- a/src/PendingObjects/UsesCall.php +++ b/src/PendingObjects/UsesCall.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Pest\PendingObjects; +use Closure; use Pest\Exceptions\InvalidUsesPath; use Pest\TestSuite; @@ -12,6 +13,13 @@ use Pest\TestSuite; */ final class UsesCall { + /** + * Contains a global before each hook closure to be executed. + * + * @var Closure + */ + private $beforeEach; + /** * Holds the class and traits. * @@ -97,11 +105,26 @@ final class UsesCall return $this; } + /** + * Sets the global beforeEach test hook + */ + public function beforeEach(Closure $hook): UsesCall + { + $this->beforeEach = $hook; + + return $this; + } + /** * Dispatch the creation of uses. */ public function __destruct() { - TestSuite::getInstance()->tests->use($this->classAndTraits, $this->groups, $this->targets); + TestSuite::getInstance()->tests->use( + $this->classAndTraits, + $this->groups, + $this->targets, + $this->beforeEach, + ); } } diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index cf36319e..f8107247 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Pest\Repositories; +use Closure; use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\TestAlreadyExist; use Pest\Exceptions\TestCaseAlreadyInUse; @@ -46,8 +47,9 @@ final class TestRepository }; foreach ($this->uses as $path => $uses) { - [$classOrTraits, $groups] = $uses; - $setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith): void { + [$classOrTraits, $groups, $beforeEach] = $uses; + + $setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $beforeEach): void { [$filename] = explode('@', $key); if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) { @@ -62,10 +64,9 @@ final class TestRepository } } - $testCase - ->factoryProxies - // Consider set the real line here. - ->add($filename, 0, 'addGroups', [$groups]); + // IDEA: Consider set the real lines on these. + $testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$beforeEach]); + $testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]); } }; @@ -81,7 +82,7 @@ final class TestRepository $state = count($onlyState) > 0 ? $onlyState : $this->state; foreach ($state as $testFactory) { - /* @var TestCaseFactory $testFactory */ + /** @var TestCaseFactory $testFactory */ $tests = $testFactory->build($testSuite); foreach ($tests as $test) { $each($test); @@ -95,8 +96,9 @@ final class TestRepository * @param array $classOrTraits * @param array $groups * @param array $paths + * @param Closure|null $beforeEach */ - public function use(array $classOrTraits, array $groups, array $paths): void + public function use(array $classOrTraits, array $groups, array $paths, ?Closure $beforeEach): void { foreach ($classOrTraits as $classOrTrait) { if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) { @@ -109,9 +111,10 @@ final class TestRepository $this->uses[$path] = [ array_merge($this->uses[$path][0], $classOrTraits), array_merge($this->uses[$path][1], $groups), + $this->uses[$path][2] ?? $beforeEach, ]; } else { - $this->uses[$path] = [$classOrTraits, $groups]; + $this->uses[$path] = [$classOrTraits, $groups, $beforeEach]; } } } From 53333b56abb6f4cdf5a274c84671fd7ec584ea66 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Tue, 6 Apr 2021 19:26:42 -0500 Subject: [PATCH 03/14] test: adding tests for beforeEach and afterEach + empty tests for *All --- tests/Hooks/AfterAllTest.php | 11 +++++++++++ tests/Hooks/AfterEachTest.php | 24 ++++++++++++++++++++++++ tests/Hooks/BeforeAllTest.php | 11 +++++++++++ tests/Hooks/BeforeEachTest.php | 24 ++++++++++++++++++++---- tests/Pest.php | 19 ++++++++++++++++--- 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 tests/Hooks/AfterAllTest.php create mode 100644 tests/Hooks/AfterEachTest.php create mode 100644 tests/Hooks/BeforeAllTest.php diff --git a/tests/Hooks/AfterAllTest.php b/tests/Hooks/AfterAllTest.php new file mode 100644 index 00000000..746ed758 --- /dev/null +++ b/tests/Hooks/AfterAllTest.php @@ -0,0 +1,11 @@ +afterAll(function () { +}); + +afterAll(function () { +}); + +test('global afterAll execution order', function () { +}); + diff --git a/tests/Hooks/AfterEachTest.php b/tests/Hooks/AfterEachTest.php new file mode 100644 index 00000000..1eea05dc --- /dev/null +++ b/tests/Hooks/AfterEachTest.php @@ -0,0 +1,24 @@ +afterEach(function () { + expect($this) + ->toHaveProperty('ith') + ->and($this->ith) + ->toBe(0); + + $this->ith = 1; +}); + +afterEach(function () { + expect($this) + ->toHaveProperty('ith') + ->and($this->ith) + ->toBe(1); +}); + +test('global afterEach execution order', function () { + expect($this) + ->not() + ->toHaveProperty('ith'); +}); + diff --git a/tests/Hooks/BeforeAllTest.php b/tests/Hooks/BeforeAllTest.php new file mode 100644 index 00000000..4c38b9b7 --- /dev/null +++ b/tests/Hooks/BeforeAllTest.php @@ -0,0 +1,11 @@ +beforeAll(function () { +}); + +beforeAll(function () { +}); + +test('global beforeAll execution order', function () { +}); + diff --git a/tests/Hooks/BeforeEachTest.php b/tests/Hooks/BeforeEachTest.php index 199d6162..e11a885b 100644 --- a/tests/Hooks/BeforeEachTest.php +++ b/tests/Hooks/BeforeEachTest.php @@ -1,11 +1,27 @@ baz)->toBe(1); // set from Pest.php global/shared hook +uses()->beforeEach(function () { + expect($this) + ->toHaveProperty('baz') + ->and($this->baz) + ->toBe(0); + + $this->baz = 1; +}); + +beforeEach(function () { + expect($this) + ->toHaveProperty('baz') + ->and($this->baz) + ->toBe(1); $this->baz = 2; }); -test('global before each', function (): void { - expect($this->baz)->toBe(2); +test('global beforeEach execution order', function () { + expect($this) + ->toHaveProperty('baz') + ->and($this->baz) + ->toBe(2); }); + diff --git a/tests/Pest.php b/tests/Pest.php index e2d51397..9c210e73 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,6 +1,19 @@ group('integration')->in('Visual'); -uses()->beforeEach(function (): void { - $this->baz = 1; -})->in('Hooks'); + +uses() + ->beforeEach(function () { + $this->baz = 0; + }) + // ->beforeAll(function () { + // dump(0); + // }) + ->afterEach(function () { + $this->ith = 0; + }) + // ->afterAll(function () { + // dump(0); + // }) + ->in('Hooks'); + From ff445895722a724a3ed18781678e253e37551777 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Tue, 6 Apr 2021 19:27:50 -0500 Subject: [PATCH 04/14] refactor: pack hooks into an array instead of 1 argument per hook --- src/Concerns/TestCase.php | 73 +++++++++++++++++++++++------ src/PendingObjects/UsesCall.php | 47 +++++++++++++++++-- src/Repositories/TestRepository.php | 17 ++++--- 3 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/Concerns/TestCase.php b/src/Concerns/TestCase.php index cc6a5f8d..8469b4b8 100644 --- a/src/Concerns/TestCase.php +++ b/src/Concerns/TestCase.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Pest\Concerns; use Closure; +use Pest\Repositories\BeforeEachRepository; use Pest\Support\ExceptionTrace; +use Pest\Support\ChainableClosure; use Pest\TestSuite; use PHPUnit\Framework\ExecutionOrderDependency; use PHPUnit\Util\Test; @@ -34,13 +36,29 @@ trait TestCase */ private $__test; + /** + * Holds a global/shared beforeEach ("set up") closure if one has been + * defined. + * + * @var Closure|null + */ + private $beforeEach = null; + + /** + * Holds a global/shared afterEach ("tear down") closure if one has been + * defined. + * + * @var Closure|null + */ + private $afterEach = null; + /** * Creates a new instance of the test case. */ public function __construct(Closure $test, string $description, array $data) { $this->__test = $test; - $this->__description = $description; + $this->__description = $description; parent::__construct('__test', $data); } @@ -55,15 +73,6 @@ trait TestCase $this->setGroups($groups); } - /** - * Add a shared/"global" before each test hook that will execute **before** - * the test defined `beforeEach` hook. - */ - public function addBeforeEach(?Closure $hook): void - { - $this->beforeEach = $hook; - } - /** * Add dependencies to the test case and map them to instances of ExecutionOrderDependency. */ @@ -82,6 +91,38 @@ trait TestCase $this->setDependencies($tests); } + /** + * Add a shared/"global" before each test hook that will execute **before** + * the test defined `beforeEach` hook. + */ + public function addBeforeEach(?Closure $hook): void + { + $this->addHook('beforeEach', $hook); + } + + /** + * Add a shared/"global" after each test hook that will execute **before** + * the test defined `afterEach` hook. + */ + public function addAfterEach(?Closure $hook): void + { + $this->addHook('afterEach', $hook); + } + + /** + * Add a shared/global hook and compose them if more than one is passed. + */ + private function addHook(string $property, ?Closure $hook): void + { + if (!$hook) { + return; + } + + $this->{$property} = ($this->{$property} instanceof Closure) + ? ChainableClosure::from($this->{$property}, $hook) + : $hook; + } + /** * Returns the test case name. Note that, in Pest * we ignore withDataset argument as the description @@ -130,12 +171,12 @@ trait TestCase parent::setUp(); - if ($this->beforeEach instanceof Closure) { - $this->__callClosure($this->beforeEach, func_get_args()); - } - $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); + if ($this->beforeEach instanceof Closure) { + $beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach); + } + $this->__callClosure($beforeEach, func_get_args()); } @@ -146,6 +187,10 @@ trait TestCase { $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); + if ($this->afterEach instanceof Closure) { + $afterEach = ChainableClosure::from($this->afterEach, $afterEach); + } + $this->__callClosure($afterEach, func_get_args()); parent::tearDown(); diff --git a/src/PendingObjects/UsesCall.php b/src/PendingObjects/UsesCall.php index d0b33b38..0d56a4cc 100644 --- a/src/PendingObjects/UsesCall.php +++ b/src/PendingObjects/UsesCall.php @@ -16,9 +16,16 @@ final class UsesCall /** * Contains a global before each hook closure to be executed. * - * @var Closure + * Array indices here matter. They are mapped as follows: + * + * - `0` => `beforeAll` + * - `1` => `beforeEach` + * - `2` => `afterEach` + * - `3` => `afterAll` + * + * @var array */ - private $beforeEach; + private $hooks = []; /** * Holds the class and traits. @@ -106,11 +113,41 @@ final class UsesCall } /** - * Sets the global beforeEach test hook + * Sets the global beforeAll test hook. + */ + public function beforeAll(Closure $hook): UsesCall + { + $this->hooks[0] = $hook; + + return $this; + } + + /** + * Sets the global beforeEach test hook. */ public function beforeEach(Closure $hook): UsesCall { - $this->beforeEach = $hook; + $this->hooks[1] = $hook; + + return $this; + } + + /** + * Sets the global afterEach test hook. + */ + public function afterEach(Closure $hook): UsesCall + { + $this->hooks[2] = $hook; + + return $this; + } + + /** + * Sets the global afterAll test hook. + */ + public function afterAll(Closure $hook): UsesCall + { + $this->hooks[3] = $hook; return $this; } @@ -124,7 +161,7 @@ final class UsesCall $this->classAndTraits, $this->groups, $this->targets, - $this->beforeEach, + $this->hooks, ); } } diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index f8107247..d9b5406d 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -47,9 +47,9 @@ final class TestRepository }; foreach ($this->uses as $path => $uses) { - [$classOrTraits, $groups, $beforeEach] = $uses; + [$classOrTraits, $groups, $hooks] = $uses; - $setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $beforeEach): void { + $setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $hooks): void { [$filename] = explode('@', $key); if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) { @@ -65,8 +65,11 @@ final class TestRepository } // IDEA: Consider set the real lines on these. - $testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$beforeEach]); $testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]); + // $testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]); + $testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$hooks[1] ?? null]); + $testCase->factoryProxies->add($filename, 0, 'addAfterEach', [$hooks[2] ?? null]); + // $testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]); } }; @@ -96,9 +99,9 @@ final class TestRepository * @param array $classOrTraits * @param array $groups * @param array $paths - * @param Closure|null $beforeEach + * @param array $hooks */ - public function use(array $classOrTraits, array $groups, array $paths, ?Closure $beforeEach): void + public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void { foreach ($classOrTraits as $classOrTrait) { if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) { @@ -111,10 +114,10 @@ final class TestRepository $this->uses[$path] = [ array_merge($this->uses[$path][0], $classOrTraits), array_merge($this->uses[$path][1], $groups), - $this->uses[$path][2] ?? $beforeEach, + $this->uses[$path][2] + $hooks, ]; } else { - $this->uses[$path] = [$classOrTraits, $groups, $beforeEach]; + $this->uses[$path] = [$classOrTraits, $groups, $hooks]; } } } From 54f9397f47ff8e30e0f16d6ea7b04fed566ece8b Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Tue, 6 Apr 2021 19:32:49 -0500 Subject: [PATCH 05/14] style: address stan + lint issues --- src/Concerns/TestCase.php | 9 ++++----- src/Repositories/TestRepository.php | 10 +++++----- tests/Hooks/AfterAllTest.php | 1 - tests/Hooks/AfterEachTest.php | 1 - tests/Hooks/BeforeAllTest.php | 1 - tests/Hooks/BeforeEachTest.php | 1 - tests/Pest.php | 3 +-- 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Concerns/TestCase.php b/src/Concerns/TestCase.php index 8469b4b8..a0ecda8a 100644 --- a/src/Concerns/TestCase.php +++ b/src/Concerns/TestCase.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace Pest\Concerns; use Closure; -use Pest\Repositories\BeforeEachRepository; -use Pest\Support\ExceptionTrace; use Pest\Support\ChainableClosure; +use Pest\Support\ExceptionTrace; use Pest\TestSuite; use PHPUnit\Framework\ExecutionOrderDependency; use PHPUnit\Util\Test; @@ -58,7 +57,7 @@ trait TestCase public function __construct(Closure $test, string $description, array $data) { $this->__test = $test; - $this->__description = $description; + $this->__description = $description; parent::__construct('__test', $data); } @@ -173,7 +172,7 @@ trait TestCase $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); - if ($this->beforeEach instanceof Closure) { + if ($this->beforeEach instanceof Closure) { $beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach); } @@ -187,7 +186,7 @@ trait TestCase { $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); - if ($this->afterEach instanceof Closure) { + if ($this->afterEach instanceof Closure) { $afterEach = ChainableClosure::from($this->afterEach, $afterEach); } diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index d9b5406d..b668b7d5 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -25,7 +25,7 @@ final class TestRepository private $state = []; /** - * @var array>> + * @var array>> */ private $uses = []; @@ -96,10 +96,10 @@ final class TestRepository /** * Uses the given `$testCaseClass` on the given `$paths`. * - * @param array $classOrTraits - * @param array $groups - * @param array $paths - * @param array $hooks + * @param array $classOrTraits + * @param array $groups + * @param array $paths + * @param array $hooks */ public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void { diff --git a/tests/Hooks/AfterAllTest.php b/tests/Hooks/AfterAllTest.php index 746ed758..ae8d61f4 100644 --- a/tests/Hooks/AfterAllTest.php +++ b/tests/Hooks/AfterAllTest.php @@ -8,4 +8,3 @@ afterAll(function () { test('global afterAll execution order', function () { }); - diff --git a/tests/Hooks/AfterEachTest.php b/tests/Hooks/AfterEachTest.php index 1eea05dc..41dc6692 100644 --- a/tests/Hooks/AfterEachTest.php +++ b/tests/Hooks/AfterEachTest.php @@ -21,4 +21,3 @@ test('global afterEach execution order', function () { ->not() ->toHaveProperty('ith'); }); - diff --git a/tests/Hooks/BeforeAllTest.php b/tests/Hooks/BeforeAllTest.php index 4c38b9b7..0eb27eb9 100644 --- a/tests/Hooks/BeforeAllTest.php +++ b/tests/Hooks/BeforeAllTest.php @@ -8,4 +8,3 @@ beforeAll(function () { test('global beforeAll execution order', function () { }); - diff --git a/tests/Hooks/BeforeEachTest.php b/tests/Hooks/BeforeEachTest.php index e11a885b..a9317cef 100644 --- a/tests/Hooks/BeforeEachTest.php +++ b/tests/Hooks/BeforeEachTest.php @@ -24,4 +24,3 @@ test('global beforeEach execution order', function () { ->and($this->baz) ->toBe(2); }); - diff --git a/tests/Pest.php b/tests/Pest.php index 9c210e73..795d906a 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -10,10 +10,9 @@ uses() // dump(0); // }) ->afterEach(function () { - $this->ith = 0; + $this->ith = 0; }) // ->afterAll(function () { // dump(0); // }) ->in('Hooks'); - From c7d26a27b6b40d33415340a9b73fcf8b53f845ab Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 09:49:12 -0500 Subject: [PATCH 06/14] fix: ensure that Pest is loaded before the test file itself --- src/Console/Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index cfd8f0d4..1d6ef21c 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -90,8 +90,6 @@ final class Command extends BaseCommand */ $this->arguments = AddsDefaults::to($this->arguments); - LoadStructure::in($this->testSuite->rootPath); - $testRunner = new TestRunner($this->arguments['loader']); $testSuite = $this->arguments['test']; @@ -127,6 +125,8 @@ final class Command extends BaseCommand */ public function run(array $argv, bool $exit = true): int { + LoadStructure::in($this->testSuite->rootPath); + $result = parent::run($argv, false); /* From f21e45ae6455d5dcf7e5f4beca3d50d335f770b9 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 09:49:53 -0500 Subject: [PATCH 07/14] fix: type declaration for and add small comment RE: merge behaviour --- src/Repositories/TestRepository.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index b668b7d5..e26af691 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -25,7 +25,7 @@ final class TestRepository private $state = []; /** - * @var array>> + * @var array>> */ private $uses = []; @@ -53,7 +53,7 @@ final class TestRepository [$filename] = explode('@', $key); if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) { - foreach ($classOrTraits as $class) { + foreach ($classOrTraits as $class) { /** @var string $class */ if (class_exists($class)) { if ($testCase->class !== TestCase::class) { throw new TestCaseAlreadyInUse($testCase->class, $class, $filename); @@ -114,7 +114,7 @@ final class TestRepository $this->uses[$path] = [ array_merge($this->uses[$path][0], $classOrTraits), array_merge($this->uses[$path][1], $groups), - $this->uses[$path][2] + $hooks, + $this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices ]; } else { $this->uses[$path] = [$classOrTraits, $groups, $hooks]; From 584a7ac8a5fcce4ec5147805b7564f82a610e919 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 10:52:49 -0500 Subject: [PATCH 08/14] test: add cases for global before/after all hooks --- tests/Hooks/AfterAllTest.php | 23 ++++++++++++++++++++--- tests/Hooks/BeforeAllTest.php | 24 +++++++++++++++++++++--- tests/Pest.php | 14 ++++++++------ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/tests/Hooks/AfterAllTest.php b/tests/Hooks/AfterAllTest.php index ae8d61f4..a34a5847 100644 --- a/tests/Hooks/AfterAllTest.php +++ b/tests/Hooks/AfterAllTest.php @@ -1,10 +1,27 @@ afterAll(function () { +global $globalHook; + +uses()->afterAll(function () use ($globalHook) { + expect($globalHook) + ->toHaveProperty('afterAll') + ->and($globalHook->afterAll) + ->toBe(0); + + $globalHook->afterAll = 1; }); -afterAll(function () { +afterAll(function () use ($globalHook) { + expect($globalHook) + ->toHaveProperty('afterAll') + ->and($globalHook->afterAll) + ->toBe(1); + + $globalHook->afterAll = 2; }); -test('global afterAll execution order', function () { +test('global afterAll execution order', function () use ($globalHook) { + expect($globalHook) + ->not() + ->toHaveProperty('afterAll'); }); diff --git a/tests/Hooks/BeforeAllTest.php b/tests/Hooks/BeforeAllTest.php index 0eb27eb9..11c996c5 100644 --- a/tests/Hooks/BeforeAllTest.php +++ b/tests/Hooks/BeforeAllTest.php @@ -1,10 +1,28 @@ beforeAll(function () { +global $globalHook; + +uses()->beforeAll(function () use ($globalHook) { + expect($globalHook) + ->toHaveProperty('beforeAll') + ->and($globalHook->beforeAll) + ->toBe(0); + + $globalHook->beforeAll = 1; }); -beforeAll(function () { +beforeAll(function () use ($globalHook) { + expect($globalHook) + ->toHaveProperty('beforeAll') + ->and($globalHook->beforeAll) + ->toBe(1); + + $globalHook->beforeAll = 2; }); -test('global beforeAll execution order', function () { +test('global beforeAll execution order', function () use ($globalHook) { + expect($globalHook) + ->toHaveProperty('beforeAll') + ->and($globalHook->beforeAll) + ->toBe(2); }); diff --git a/tests/Pest.php b/tests/Pest.php index 795d906a..a8cd868d 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -2,17 +2,19 @@ uses()->group('integration')->in('Visual'); +$globalHook = (object) []; // NOTE: global test value container to be mutated and checked across files, as needed + uses() ->beforeEach(function () { $this->baz = 0; }) - // ->beforeAll(function () { - // dump(0); - // }) + ->beforeAll(function () use ($globalHook) { + $globalHook->beforeAll = 0; + }) ->afterEach(function () { $this->ith = 0; }) - // ->afterAll(function () { - // dump(0); - // }) + ->afterAll(function () use ($globalHook) { + $globalHook->afterAll = 0; + }) ->in('Hooks'); From 7d35ee99981a8507f835ff7d1b182273bc4fef0c Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 10:53:46 -0500 Subject: [PATCH 09/14] feat: add new helper to create static closure chains --- src/Support/ChainableClosure.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index fea5533b..a791d0ba 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -23,4 +23,14 @@ final class ChainableClosure call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args()); }; } + + public static function fromStatic(Closure $closure, Closure $next): Closure + { + return static function () use ($closure, $next): void { + /* @phpstan-ignore-next-line */ + call_user_func_array(Closure::bind($closure, null, self::class), func_get_args()); + /* @phpstan-ignore-next-line */ + call_user_func_array(Closure::bind($next, null, self::class), func_get_args()); + }; + } } From 90efcc8a8ad10190895c000677f3e85ddccc6712 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 10:55:19 -0500 Subject: [PATCH 10/14] feat: add shared/global beforeAll and afterAll hooks --- src/Concerns/TestCase.php | 54 +++++++++++++++++++++++++++++ src/Repositories/TestRepository.php | 4 +-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Concerns/TestCase.php b/src/Concerns/TestCase.php index a0ecda8a..1272d49c 100644 --- a/src/Concerns/TestCase.php +++ b/src/Concerns/TestCase.php @@ -51,6 +51,22 @@ trait TestCase */ private $afterEach = null; + /** + * Holds a global/shared beforeAll ("set up before") closure if one has been + * defined. + * + * @var Closure|null + */ + private static $beforeAll = null; + + /** + * Holds a global/shared afterAll ("tear down after") closure if one has + * been defined. + * + * @var Closure|null + */ + private static $afterAll = null; + /** * Creates a new instance of the test case. */ @@ -90,6 +106,36 @@ trait TestCase $this->setDependencies($tests); } + /** + * Add a shared/"global" before all test hook that will execute **before** + * the test defined `beforeAll` hook(s). + */ + public function addBeforeAll(?Closure $hook): void + { + if (!$hook) { + return; + } + + self::$beforeAll = (self::$beforeAll instanceof Closure) + ? ChainableClosure::fromStatic(self::$beforeAll, $hook) + : $hook; + } + + /** + * Add a shared/"global" after all test hook that will execute **before** + * the test defined `afterAll` hook(s). + */ + public function addAfterAll(?Closure $hook): void + { + if (!$hook) { + return; + } + + self::$afterAll = (self::$afterAll instanceof Closure) + ? ChainableClosure::fromStatic(self::$afterAll, $hook) + : $hook; + } + /** * Add a shared/"global" before each test hook that will execute **before** * the test defined `beforeEach` hook. @@ -146,6 +192,10 @@ trait TestCase $beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename); + if (self::$beforeAll instanceof Closure) { + $beforeAll = ChainableClosure::fromStatic(self::$beforeAll, $beforeAll); + } + call_user_func(Closure::bind($beforeAll, null, self::class)); } @@ -156,6 +206,10 @@ trait TestCase { $afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename); + if (self::$afterAll instanceof Closure) { + $afterAll = ChainableClosure::fromStatic(self::$afterAll, $afterAll); + } + call_user_func(Closure::bind($afterAll, null, self::class)); parent::tearDownAfterClass(); diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index e26af691..ab49291e 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -66,10 +66,10 @@ final class TestRepository // IDEA: Consider set the real lines on these. $testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]); - // $testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]); + $testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]); $testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$hooks[1] ?? null]); $testCase->factoryProxies->add($filename, 0, 'addAfterEach', [$hooks[2] ?? null]); - // $testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]); + $testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]); } }; From 885c9f1f062e4952a1421f661cb39f9f2730e985 Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 10:56:19 -0500 Subject: [PATCH 11/14] test: update the snapshot with new test output --- tests/.snapshots/success.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index f4db6877..625557a8 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -103,6 +103,18 @@ PASS Tests\Fixtures\ExampleTest ✓ it example 2 + PASS Tests\Hooks\AfterAllTest + ✓ global afterAll execution order + + PASS Tests\Hooks\AfterEachTest + ✓ global afterEach execution order + + PASS Tests\Hooks\BeforeAllTest + ✓ global beforeAll execution order + + PASS Tests\Hooks\BeforeEachTest + ✓ global beforeEach execution order + PASS Tests\PHPUnit\CustomAffixes\InvalidTestName ✓ it runs file names like `@#$%^&()-_=+.php` @@ -209,5 +221,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 7 skipped, 115 passed + Tests: 7 skipped, 119 passed \ No newline at end of file From 1b9162151c0ed137e6084a21d3cd24c808bd440b Mon Sep 17 00:00:00 2001 From: jordanbrauer <18744334+jordanbrauer@users.noreply.github.com> Date: Wed, 7 Apr 2021 11:02:00 -0500 Subject: [PATCH 12/14] docs: add some method documentation and fix typo --- src/Support/ChainableClosure.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index a791d0ba..0dc40275 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -12,7 +12,7 @@ use Closure; final class ChainableClosure { /** - * Calls the given `$closure` and chains the the `$next` closure. + * Calls the given `$closure` and chains the `$next` closure. */ public static function from(Closure $closure, Closure $next): Closure { @@ -24,6 +24,9 @@ final class ChainableClosure }; } + /** + * Call the given static `$closure` and chains the `$next` closure. + */ public static function fromStatic(Closure $closure, Closure $next): Closure { return static function () use ($closure, $next): void { From 14cee66dfdf19054b2eea6d9baf0797b1819d906 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 20 Apr 2021 19:32:33 +0100 Subject: [PATCH 13/14] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 45706ddd..765fcafe 100644 --- a/README.md +++ b/README.md @@ -23,5 +23,6 @@ We would like to extend our thanks to the following sponsors for funding Pest de - **[Scout APM](https://scoutapm.com)** - **[Akaunting](https://akaunting.com)** +- **[Meema](https://meema.io/)** Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**. From c6a2e3b4d0eb129ad8eab2d6c8603a5e6c68f12b Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 2 May 2021 23:41:59 +0100 Subject: [PATCH 14/14] chore: release 1.1.0 --- CHANGELOG.md | 4 ++++ src/Pest.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1cb37d8..ebf3c155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [v1.1.0 (2021-05-02)](https://github.com/pestphp/pest/compare/v1.0.5...v1.1.0) +### Added +- Possibility of "hooks" being added using the "uses" function ([#282](https://github.com/pestphp/pest/pull/282)) + ## [v1.0.5 (2021-03-31)](https://github.com/pestphp/pest/compare/v1.0.4...v1.0.5) ### Added - Add `--browse` option to `pest:dusk` command ([#280](https://github.com/pestphp/pest/pull/280)) diff --git a/src/Pest.php b/src/Pest.php index 6e63d622..cc7bca60 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,5 +6,5 @@ namespace Pest; function version(): string { - return '1.0.5'; + return '1.1.0'; }