Merge branch 'master' into master

This commit is contained in:
Gregori Piñeres
2021-05-03 11:47:36 -04:00
committed by GitHub
14 changed files with 348 additions and 20 deletions

View File

@ -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))

View File

@ -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)**.

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Pest\Concerns;
use Closure;
use Pest\Support\ChainableClosure;
use Pest\Support\ExceptionTrace;
use Pest\TestSuite;
use PHPUnit\Framework\ExecutionOrderDependency;
@ -34,6 +35,38 @@ 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;
/**
* 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.
*/
@ -73,6 +106,68 @@ 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.
*/
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
@ -97,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));
}
@ -107,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();
@ -123,6 +226,10 @@ trait TestCase
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
if ($this->beforeEach instanceof Closure) {
$beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach);
}
$this->__callClosure($beforeEach, func_get_args());
}
@ -133,6 +240,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();

View File

@ -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);
/*

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Pest\PendingObjects;
use Closure;
use Pest\TestSuite;
/**
@ -11,6 +12,20 @@ use Pest\TestSuite;
*/
final class UsesCall
{
/**
* Contains a global before each hook closure to be executed.
*
* Array indices here matter. They are mapped as follows:
*
* - `0` => `beforeAll`
* - `1` => `beforeEach`
* - `2` => `afterEach`
* - `3` => `afterAll`
*
* @var array<int, Closure>
*/
private $hooks = [];
/**
* Holds the class and traits.
*
@ -95,11 +110,56 @@ final class UsesCall
return $this;
}
/**
* 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->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;
}
/**
* 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->hooks,
);
}
}

View File

@ -6,5 +6,5 @@ namespace Pest;
function version(): string
{
return '1.0.5';
return '1.1.0';
}

View File

@ -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;
@ -24,7 +25,7 @@ final class TestRepository
private $state = [];
/**
* @var array<string, array<int, array<int, string>>>
* @var array<string, array<int, array<int, string|Closure>>>
*/
private $uses = [];
@ -46,12 +47,13 @@ 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, $hooks] = $uses;
$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))) {
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);
@ -62,10 +64,12 @@ 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, '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]);
}
};
@ -81,7 +85,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);
@ -92,11 +96,12 @@ final class TestRepository
/**
* Uses the given `$testCaseClass` on the given `$paths`.
*
* @param array<int, string> $classOrTraits
* @param array<int, string> $groups
* @param array<int, string> $paths
* @param array<int, string> $classOrTraits
* @param array<int, string> $groups
* @param array<int, string> $paths
* @param array<int, Closure> $hooks
*/
public function use(array $classOrTraits, array $groups, array $paths): void
public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void
{
foreach ($classOrTraits as $classOrTrait) {
if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) {
@ -109,9 +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] + $hooks, // NOTE: array_merge will destroy numeric indices
];
} else {
$this->uses[$path] = [$classOrTraits, $groups];
$this->uses[$path] = [$classOrTraits, $groups, $hooks];
}
}
}

View File

@ -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
{
@ -23,4 +23,17 @@ final class ChainableClosure
call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args());
};
}
/**
* 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 {
/* @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());
};
}
}

View File

@ -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

View File

@ -0,0 +1,27 @@
<?php
global $globalHook;
uses()->afterAll(function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('afterAll')
->and($globalHook->afterAll)
->toBe(0);
$globalHook->afterAll = 1;
});
afterAll(function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('afterAll')
->and($globalHook->afterAll)
->toBe(1);
$globalHook->afterAll = 2;
});
test('global afterAll execution order', function () use ($globalHook) {
expect($globalHook)
->not()
->toHaveProperty('afterAll');
});

View File

@ -0,0 +1,23 @@
<?php
uses()->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');
});

View File

@ -0,0 +1,28 @@
<?php
global $globalHook;
uses()->beforeAll(function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('beforeAll')
->and($globalHook->beforeAll)
->toBe(0);
$globalHook->beforeAll = 1;
});
beforeAll(function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('beforeAll')
->and($globalHook->beforeAll)
->toBe(1);
$globalHook->beforeAll = 2;
});
test('global beforeAll execution order', function () use ($globalHook) {
expect($globalHook)
->toHaveProperty('beforeAll')
->and($globalHook->beforeAll)
->toBe(2);
});

View File

@ -0,0 +1,26 @@
<?php
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 beforeEach execution order', function () {
expect($this)
->toHaveProperty('baz')
->and($this->baz)
->toBe(2);
});

View File

@ -1,3 +1,20 @@
<?php
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 () use ($globalHook) {
$globalHook->beforeAll = 0;
})
->afterEach(function () {
$this->ith = 0;
})
->afterAll(function () use ($globalHook) {
$globalHook->afterAll = 0;
})
->in('Hooks');