mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Merge branch '2.x' into dirty_integration
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -12,3 +12,4 @@ phpstan.neon export-ignore
|
|||||||
CHANGELOG.md export-ignore
|
CHANGELOG.md export-ignore
|
||||||
CONTRIBUTING.md export-ignore
|
CONTRIBUTING.md export-ignore
|
||||||
README.md export-ignore
|
README.md export-ignore
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/static.yml
vendored
3
.github/workflows/static.yml
vendored
@ -28,5 +28,8 @@ jobs:
|
|||||||
- name: Types
|
- name: Types
|
||||||
run: composer test:types
|
run: composer test:types
|
||||||
|
|
||||||
|
- name: Refacto
|
||||||
|
run: composer test:refacto
|
||||||
|
|
||||||
- name: Style
|
- name: Style
|
||||||
run: composer test:lint
|
run: composer test:lint
|
||||||
|
|||||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest] # (macos-latest, windows-latest) 2.x-dev is under development
|
os: [ubuntu-latest, macos-latest] # "windows-latest" is waiting for https://github.com/pestphp/pest/issues/638
|
||||||
php: ['8.1', '8.2']
|
php: ['8.1', '8.2']
|
||||||
dependency-version: [prefer-lowest, prefer-stable]
|
dependency-version: [prefer-lowest, prefer-stable]
|
||||||
parallel: ['', '--parallel']
|
parallel: ['', '--parallel']
|
||||||
@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: test:parallel
|
run: test:parallel
|
||||||
if: ${{ false }} # 2.x-dev is under development
|
if: ${{ false }} # Parallel's not yet supported.
|
||||||
|
|
||||||
- name: Integration Tests
|
- name: Integration Tests
|
||||||
run: composer test:integration
|
run: composer test:integration
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/pestphp/art/master/readme.png" width="600" alt="PEST">
|
<img src="https://raw.githubusercontent.com/pestphp/art/master/readme.png" width="600" alt="PEST">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/workflow/status/pestphp/pest/Tests/master"></a>
|
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=2.x&label=Tests%202.x"></a>
|
||||||
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a>
|
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a>
|
||||||
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a>
|
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a>
|
||||||
<a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>
|
<a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>
|
||||||
|
|||||||
5
TODO.md
5
TODO.md
@ -1,5 +0,0 @@
|
|||||||
1. TeamCity. (oliver)
|
|
||||||
2. Junit. (nuno)
|
|
||||||
3. Check all plugins. (nuno - almost done)
|
|
||||||
4. Parallel testing. (luke)
|
|
||||||
5. Finish Collision's todo. (nuno)
|
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1.0",
|
"php": "^8.1.0",
|
||||||
"nunomaduro/collision": "^7.0.0",
|
"nunomaduro/collision": "^7.0.0",
|
||||||
"nunomaduro/termwind": "^1.14.2",
|
"nunomaduro/termwind": "^1.15",
|
||||||
"pestphp/pest-plugin": "^2.0.0",
|
"pestphp/pest-plugin": "^2.0.0",
|
||||||
"phpunit/phpunit": "10.0.x-dev"
|
"phpunit/phpunit": "10.0.x-dev"
|
||||||
},
|
},
|
||||||
@ -27,15 +27,7 @@
|
|||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Pest\\": "src/"
|
"Pest\\": "src/"
|
||||||
},
|
},
|
||||||
"exclude-from-classmap": [
|
|
||||||
"../phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php",
|
|
||||||
"vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php",
|
|
||||||
"../phpunit/src/Runner/TestSuiteLoader.php",
|
|
||||||
"vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php"
|
|
||||||
],
|
|
||||||
"files": [
|
"files": [
|
||||||
"overrides/Runner/Filter/NameFilterIterator.php",
|
|
||||||
"overrides/Runner/TestSuiteLoader.php",
|
|
||||||
"src/Functions.php",
|
"src/Functions.php",
|
||||||
"src/Pest.php"
|
"src/Pest.php"
|
||||||
]
|
]
|
||||||
@ -50,7 +42,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"pestphp/pest-dev-tools": "^2.0.0",
|
"pestphp/pest-dev-tools": "^2.1.0",
|
||||||
|
"pestphp/pest-plugin-arch": "^2.0.0",
|
||||||
"symfony/process": "^6.2.0"
|
"symfony/process": "^6.2.0"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
@ -66,14 +59,18 @@
|
|||||||
"bin/pest"
|
"bin/pest"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"refacto": "rector",
|
||||||
"lint": "pint",
|
"lint": "pint",
|
||||||
|
"test:refacto": "rector --dry-run",
|
||||||
"test:lint": "pint --test",
|
"test:lint": "pint --test",
|
||||||
"test:types": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
"test:types": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
||||||
"test:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
|
"test:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
|
||||||
|
"test:inline": "php bin/pest --colors=always --configuration=phpunit.inline.xml",
|
||||||
"test:parallel": "exit 1",
|
"test:parallel": "exit 1",
|
||||||
"test:integration": "php bin/pest --colors=always --group=integration -v",
|
"test:integration": "php bin/pest --colors=always --group=integration -v",
|
||||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
|
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
|
||||||
"test": [
|
"test": [
|
||||||
|
"@test:refacto",
|
||||||
"@test:lint",
|
"@test:lint",
|
||||||
"@test:types",
|
"@test:types",
|
||||||
"@test:unit",
|
"@test:unit",
|
||||||
|
|||||||
@ -98,14 +98,14 @@ final class TestSuiteLoader
|
|||||||
|
|
||||||
self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses);
|
self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses);
|
||||||
|
|
||||||
if (empty(self::$loadedClasses)) {
|
if (empty($loadedClasses)) {
|
||||||
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
$testCaseFound = false;
|
$testCaseFound = false;
|
||||||
|
|
||||||
foreach (self::$loadedClasses as $loadedClass) {
|
foreach (array_reverse($loadedClasses) as $loadedClass) {
|
||||||
if (is_subclass_of($loadedClass, HasPrintableTestCaseName::class)) {
|
if (is_subclass_of($loadedClass, HasPrintableTestCaseName::class) || is_subclass_of($loadedClass, TestCase::class)) {
|
||||||
$suiteClassName = $loadedClass;
|
$suiteClassName = $loadedClass;
|
||||||
|
|
||||||
$testCaseFound = true;
|
$testCaseFound = true;
|
||||||
@ -115,13 +115,7 @@ final class TestSuiteLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! $testCaseFound) {
|
if (! $testCaseFound) {
|
||||||
foreach (self::$loadedClasses as $loadedClass) {
|
return $this->exceptionFor($suiteClassName, $suiteClassFile);
|
||||||
if (is_subclass_of($loadedClass, TestCase::class)) {
|
|
||||||
$suiteClassName = $loadedClass;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! class_exists($suiteClassName, false)) {
|
if (! class_exists($suiteClassName, false)) {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ parameters:
|
|||||||
- "#with a nullable type declaration#"
|
- "#with a nullable type declaration#"
|
||||||
- "#type mixed is not subtype of native#"
|
- "#type mixed is not subtype of native#"
|
||||||
- "#is not allowed to extend#"
|
- "#is not allowed to extend#"
|
||||||
- "#Language construct eval#"
|
|
||||||
- "# with null as default value#"
|
- "# with null as default value#"
|
||||||
- "#has parameter \\$closure with default value.#"
|
- "#has parameter \\$closure with default value.#"
|
||||||
- "#has parameter \\$description with default value.#"
|
- "#has parameter \\$description with default value.#"
|
||||||
|
|||||||
27
rector.php
Normal file
27
rector.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
|
||||||
|
use Rector\Config\RectorConfig;
|
||||||
|
use Rector\Set\ValueObject\LevelSetList;
|
||||||
|
use Rector\Set\ValueObject\SetList;
|
||||||
|
|
||||||
|
return static function (RectorConfig $rectorConfig): void {
|
||||||
|
$rectorConfig->paths([
|
||||||
|
__DIR__.'/src',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rectorConfig->rules([
|
||||||
|
InlineConstructorDefaultToPropertyRector::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rectorConfig->sets([
|
||||||
|
LevelSetList::UP_TO_PHP_81,
|
||||||
|
SetList::CODE_QUALITY,
|
||||||
|
SetList::DEAD_CODE,
|
||||||
|
SetList::EARLY_RETURN,
|
||||||
|
SetList::TYPE_DECLARATION,
|
||||||
|
SetList::PRIVATIZATION,
|
||||||
|
]);
|
||||||
|
};
|
||||||
@ -5,16 +5,17 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Bootstrappers;
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
use NunoMaduro\Collision;
|
use NunoMaduro\Collision;
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootExceptionHandler
|
final class BootExceptionHandler implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Boots the Exception Handler.
|
* Boots the Exception Handler.
|
||||||
*/
|
*/
|
||||||
public function __invoke(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$handler = new Collision\Provider();
|
$handler = new Collision\Provider();
|
||||||
|
|
||||||
|
|||||||
@ -4,16 +4,19 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Bootstrappers;
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
|
use Pest\Support\DatasetInfo;
|
||||||
use Pest\Support\Str;
|
use Pest\Support\Str;
|
||||||
use function Pest\testDirectory;
|
use function Pest\testDirectory;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use RecursiveDirectoryIterator;
|
use RecursiveDirectoryIterator;
|
||||||
use RecursiveIteratorIterator;
|
use RecursiveIteratorIterator;
|
||||||
|
use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootFiles
|
final class BootFiles implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The Pest convention.
|
* The Pest convention.
|
||||||
@ -21,8 +24,6 @@ final class BootFiles
|
|||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
private const STRUCTURE = [
|
private const STRUCTURE = [
|
||||||
'Datasets',
|
|
||||||
'Datasets.php',
|
|
||||||
'Expectations',
|
'Expectations',
|
||||||
'Expectations.php',
|
'Expectations.php',
|
||||||
'Helpers',
|
'Helpers',
|
||||||
@ -33,7 +34,7 @@ final class BootFiles
|
|||||||
/**
|
/**
|
||||||
* Boots the Subscribers.
|
* Boots the Subscribers.
|
||||||
*/
|
*/
|
||||||
public function __invoke(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$rootPath = TestSuite::getInstance()->rootPath;
|
$rootPath = TestSuite::getInstance()->rootPath;
|
||||||
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
|
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
|
||||||
@ -56,6 +57,8 @@ final class BootFiles
|
|||||||
$this->load($filename);
|
$this->load($filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->bootDatasets($testsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,4 +76,15 @@ final class BootFiles
|
|||||||
|
|
||||||
include_once $filename;
|
include_once $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function bootDatasets(string $testsPath): void
|
||||||
|
{
|
||||||
|
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (DatasetInfo::isADatasetsFile($file) || DatasetInfo::isInsideADatasetsDirectory($file)) {
|
||||||
|
$this->load($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/Bootstrappers/BootOverrides.php
Normal file
40
src/Bootstrappers/BootOverrides.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
|
use Pest\Exceptions\ShouldNotHappen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BootOverrides implements Bootstrapper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The list of files to be overridden.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
private const FILES = [
|
||||||
|
'Runner/Filter/NameFilterIterator.php',
|
||||||
|
'Runner/TestSuiteLoader.php',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boots the Subscribers.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
foreach (self::FILES as $file) {
|
||||||
|
$file = __DIR__."/../../overrides/$file";
|
||||||
|
|
||||||
|
if (! file_exists($file)) {
|
||||||
|
throw ShouldNotHappen::fromMessage(sprintf('File [%s] does not exist.', $file));
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Bootstrappers;
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
use Pest\Subscribers;
|
use Pest\Subscribers;
|
||||||
use PHPUnit\Event;
|
use PHPUnit\Event;
|
||||||
use PHPUnit\Event\Subscriber;
|
use PHPUnit\Event\Subscriber;
|
||||||
@ -11,7 +12,7 @@ use PHPUnit\Event\Subscriber;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootSubscribers
|
final class BootSubscribers implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The Kernel subscribers.
|
* The Kernel subscribers.
|
||||||
@ -22,13 +23,14 @@ final class BootSubscribers
|
|||||||
Subscribers\EnsureConfigurationIsValid::class,
|
Subscribers\EnsureConfigurationIsValid::class,
|
||||||
Subscribers\EnsureConfigurationDefaults::class,
|
Subscribers\EnsureConfigurationDefaults::class,
|
||||||
Subscribers\EnsureRetryRepositoryExists::class,
|
Subscribers\EnsureRetryRepositoryExists::class,
|
||||||
Subscribers\EnsureFailedTestsAreStoredForRetry::class,
|
Subscribers\EnsureErroredTestsAreRetryable::class,
|
||||||
|
Subscribers\EnsureFailedTestsAreRetryable::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boots the Subscribers.
|
* Boots the Subscribers.
|
||||||
*/
|
*/
|
||||||
public function __invoke(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
foreach (self::SUBSCRIBERS as $subscriber) {
|
foreach (self::SUBSCRIBERS as $subscriber) {
|
||||||
Event\Facade::registerSubscriber(
|
Event\Facade::registerSubscriber(
|
||||||
|
|||||||
@ -4,13 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Bootstrappers;
|
namespace Pest\Bootstrappers;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
use Pest\Support\View;
|
use Pest\Support\View;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class BootView
|
final class BootView implements Bootstrapper
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly OutputInterface $output
|
private readonly OutputInterface $output
|
||||||
@ -21,7 +22,7 @@ final class BootView
|
|||||||
/**
|
/**
|
||||||
* Boots the view renderer.
|
* Boots the view renderer.
|
||||||
*/
|
*/
|
||||||
public function __invoke(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
View::renderUsing($this->output);
|
View::renderUsing($this->output);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,6 @@ trait Pipeable
|
|||||||
*/
|
*/
|
||||||
private function pipes(string $name, object $context, string $scope): array
|
private function pipes(string $name, object $context, string $scope): array
|
||||||
{
|
{
|
||||||
return array_map(fn (Closure $pipe) => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ trait Retrievable
|
|||||||
* @template TRetrievableValue
|
* @template TRetrievableValue
|
||||||
*
|
*
|
||||||
* Safely retrieve the value at the given key from an object or array.
|
* Safely retrieve the value at the given key from an object or array.
|
||||||
*
|
|
||||||
* @template TRetrievableValue
|
* @template TRetrievableValue
|
||||||
*
|
*
|
||||||
* @param array<string, TRetrievableValue>|object $value
|
* @param array<string, TRetrievableValue>|object $value
|
||||||
|
|||||||
@ -259,7 +259,7 @@ trait Testable
|
|||||||
*/
|
*/
|
||||||
private function __callClosure(Closure $closure, array $arguments): mixed
|
private function __callClosure(Closure $closure, array $arguments): mixed
|
||||||
{
|
{
|
||||||
return ExceptionTrace::ensure(fn () => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Console;
|
namespace Pest\Console;
|
||||||
|
|
||||||
|
use Pest\Bootstrappers\BootView;
|
||||||
use Pest\Support\View;
|
use Pest\Support\View;
|
||||||
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
@ -39,6 +40,9 @@ final class Thanks
|
|||||||
*/
|
*/
|
||||||
public function __invoke(): void
|
public function __invoke(): void
|
||||||
{
|
{
|
||||||
|
$bootstrapper = new BootView($this->output);
|
||||||
|
$bootstrapper->boot();
|
||||||
|
|
||||||
$wantsToSupport = (new SymfonyQuestionHelper())->ask(
|
$wantsToSupport = (new SymfonyQuestionHelper())->ask(
|
||||||
new ArrayInput([]),
|
new ArrayInput([]),
|
||||||
$this->output,
|
$this->output,
|
||||||
|
|||||||
16
src/Contracts/Bootstrapper.php
Normal file
16
src/Contracts/Bootstrapper.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface Bootstrapper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Boots the bootstrapper.
|
||||||
|
*/
|
||||||
|
public function boot(): void;
|
||||||
|
}
|
||||||
@ -12,13 +12,13 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class DatasetAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a new Exception instance.
|
* Creates a new Exception instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $name)
|
public function __construct(string $name, string $scope)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('A dataset with the name `%s` already exist.', $name));
|
parent::__construct(sprintf('A dataset with the name `%s` already exist in scope [%s].', $name, $scope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
src/Exceptions/InvalidExpectation.php
Normal file
26
src/Exceptions/InvalidExpectation.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use LogicException;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class InvalidExpectation extends LogicException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $methods
|
||||||
|
*
|
||||||
|
* @throws self
|
||||||
|
*/
|
||||||
|
public static function fromMethods(array $methods): never
|
||||||
|
{
|
||||||
|
throw new self(sprintf('Expectation [%s] is not valid.', implode('->', $methods)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,11 +12,9 @@ use InvalidArgumentException;
|
|||||||
final class InvalidExpectationValue extends InvalidArgumentException
|
final class InvalidExpectationValue extends InvalidArgumentException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return never
|
|
||||||
*
|
|
||||||
* @throws self
|
* @throws self
|
||||||
*/
|
*/
|
||||||
public static function expected(string $type): void
|
public static function expected(string $type): never
|
||||||
{
|
{
|
||||||
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
|
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,18 @@ namespace Pest;
|
|||||||
|
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
|
use Pest\Arch\Expectations\ToBeUsedOn;
|
||||||
|
use Pest\Arch\Expectations\ToBeUsedOnNothing;
|
||||||
|
use Pest\Arch\Expectations\ToOnlyBeUsedOn;
|
||||||
|
use Pest\Arch\Expectations\ToOnlyUse;
|
||||||
|
use Pest\Arch\Expectations\ToUse;
|
||||||
|
use Pest\Arch\Expectations\ToUseNothing;
|
||||||
use Pest\Concerns\Extendable;
|
use Pest\Concerns\Extendable;
|
||||||
use Pest\Concerns\Pipeable;
|
use Pest\Concerns\Pipeable;
|
||||||
use Pest\Concerns\Retrievable;
|
use Pest\Concerns\Retrievable;
|
||||||
use Pest\Exceptions\ExpectationNotFound;
|
use Pest\Exceptions\ExpectationNotFound;
|
||||||
|
use Pest\Exceptions\InvalidExpectation;
|
||||||
use Pest\Exceptions\InvalidExpectationValue;
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
use Pest\Expectations\EachExpectation;
|
use Pest\Expectations\EachExpectation;
|
||||||
use Pest\Expectations\HigherOrderExpectation;
|
use Pest\Expectations\HigherOrderExpectation;
|
||||||
@ -24,7 +32,7 @@ use PHPUnit\Framework\ExpectationFailedException;
|
|||||||
*
|
*
|
||||||
* @template TValue
|
* @template TValue
|
||||||
*
|
*
|
||||||
* @property Expectation $not Creates the opposite expectation.
|
* @property OppositeExpectation $not Creates the opposite expectation.
|
||||||
* @property EachExpectation $each Creates an expectation on each element on the traversable value.
|
* @property EachExpectation $each Creates an expectation on each element on the traversable value.
|
||||||
*
|
*
|
||||||
* @mixin Mixins\Expectation<TValue>
|
* @mixin Mixins\Expectation<TValue>
|
||||||
@ -70,10 +78,12 @@ final class Expectation
|
|||||||
InvalidExpectationValue::expected('string');
|
InvalidExpectationValue::expected('string');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var array<int|string, mixed>|bool $value */
|
$this->toBeJson();
|
||||||
$value = json_decode($this->value, true, 512);
|
|
||||||
|
|
||||||
return $this->toBeJson()->and($value);
|
/** @var array<int|string, mixed>|bool $value */
|
||||||
|
$value = json_decode($this->value, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
return $this->and($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -348,4 +358,65 @@ final class Expectation
|
|||||||
{
|
{
|
||||||
return new Any();
|
return new Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation target use the given dependencies.
|
||||||
|
*
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toUse(array|string $targets): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToUse::make($this, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation target "only" use on the given dependencies.
|
||||||
|
*
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toOnlyUse(array|string $targets): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToOnlyUse::make($this, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation target does not use any dependencies.
|
||||||
|
*/
|
||||||
|
public function toUseNothing(): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToUseNothing::make($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toBeUsed(): never
|
||||||
|
{
|
||||||
|
throw InvalidExpectation::fromMethods(['toBeUsed']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation dependency is used by the given targets.
|
||||||
|
*
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toBeUsedOn(array|string $targets): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToBeUsedOn::make($this, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation dependency is "only" used by the given targets.
|
||||||
|
*
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toOnlyBeUsedOn(array|string $targets): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToOnlyBeUsedOn::make($this, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation dependency is not used.
|
||||||
|
*/
|
||||||
|
public function toBeUsedOnNothing(): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToBeUsedOnNothing::make($this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Expectations;
|
namespace Pest\Expectations;
|
||||||
|
|
||||||
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
|
use Pest\Arch\Expectations\ToBeUsedOn;
|
||||||
|
use Pest\Arch\Expectations\ToBeUsedOnNothing;
|
||||||
|
use Pest\Arch\Expectations\ToUse;
|
||||||
|
use Pest\Arch\GroupArchExpectation;
|
||||||
|
use Pest\Arch\SingleArchExpectation;
|
||||||
|
use Pest\Exceptions\InvalidExpectation;
|
||||||
use Pest\Expectation;
|
use Pest\Expectation;
|
||||||
use Pest\Support\Arr;
|
use Pest\Support\Arr;
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
@ -52,6 +59,64 @@ final class OppositeExpectation
|
|||||||
return $this->original;
|
return $this->original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation target does not use any of the given dependencies.
|
||||||
|
*
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toUse(array|string $targets): ArchExpectation
|
||||||
|
{
|
||||||
|
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($this->original, $target)->opposite(
|
||||||
|
fn () => $this->throwExpectationFailedException('toUse', $target),
|
||||||
|
), is_string($targets) ? [$targets] : $targets));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toOnlyUse(array|string $targets): never
|
||||||
|
{
|
||||||
|
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toUseNothing(): never
|
||||||
|
{
|
||||||
|
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation dependency is not used.
|
||||||
|
*/
|
||||||
|
public function toBeUsed(): ArchExpectation
|
||||||
|
{
|
||||||
|
return ToBeUsedOnNothing::make($this->original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation dependency is not used by any of the given targets.
|
||||||
|
*
|
||||||
|
* @param array<int, string>|string $targets
|
||||||
|
*/
|
||||||
|
public function toBeUsedOn(array|string $targets): ArchExpectation
|
||||||
|
{
|
||||||
|
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedOn::make($this->original, $target)->opposite(
|
||||||
|
fn () => $this->throwExpectationFailedException('toBeUsedOn', $target),
|
||||||
|
), is_string($targets) ? [$targets] : $targets));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toOnlyBeUsedOn(): never
|
||||||
|
{
|
||||||
|
throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedOn']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given expectation dependency is not used.
|
||||||
|
*/
|
||||||
|
public function toBeUsedOnNothing(): never
|
||||||
|
{
|
||||||
|
throw InvalidExpectation::fromMethods(['not', 'toBeUsedOnNothing']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dynamic method calls into the original expectation.
|
* Handle dynamic method calls into the original expectation.
|
||||||
*
|
*
|
||||||
@ -89,11 +154,12 @@ final class OppositeExpectation
|
|||||||
/**
|
/**
|
||||||
* Creates a new expectation failed exception with a nice readable message.
|
* Creates a new expectation failed exception with a nice readable message.
|
||||||
*
|
*
|
||||||
* @param array<int, mixed> $arguments
|
* @param array<int, mixed>|string $arguments
|
||||||
* @return never
|
|
||||||
*/
|
*/
|
||||||
private function throwExpectationFailedException(string $name, array $arguments = []): void
|
public function throwExpectationFailedException(string $name, array|string $arguments = []): never
|
||||||
{
|
{
|
||||||
|
$arguments = is_array($arguments) ? $arguments : [$arguments];
|
||||||
|
|
||||||
$exporter = new Exporter();
|
$exporter = new Exporter();
|
||||||
|
|
||||||
$toString = fn ($argument): string => $exporter->shortenedExport($argument);
|
$toString = fn ($argument): string => $exporter->shortenedExport($argument);
|
||||||
|
|||||||
@ -13,10 +13,8 @@ abstract class Attribute
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Determine if the attribute should be placed above the class instead of above the method.
|
* Determine if the attribute should be placed above the class instead of above the method.
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
*/
|
||||||
public const ABOVE_CLASS = false;
|
public static bool $above = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, string> $attributes
|
* @param array<int, string> $attributes
|
||||||
|
|||||||
@ -15,10 +15,8 @@ final class Covers extends Attribute
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Determine if the attribute should be placed above the classe instead of above the method.
|
* Determine if the attribute should be placed above the classe instead of above the method.
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
*/
|
||||||
public const ABOVE_CLASS = true;
|
public static bool $above = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds attributes regarding the "covers" feature.
|
* Adds attributes regarding the "covers" feature.
|
||||||
|
|||||||
@ -85,7 +85,7 @@ final class TestCaseFactory
|
|||||||
|
|
||||||
$methods = array_values(array_filter(
|
$methods = array_values(array_filter(
|
||||||
$this->methods,
|
$this->methods,
|
||||||
fn ($method) => $methodsUsingOnly === [] || in_array($method, $methodsUsingOnly, true)
|
fn ($method): bool => $methodsUsingOnly === [] || in_array($method, $methodsUsingOnly, true)
|
||||||
));
|
));
|
||||||
|
|
||||||
if ($methods !== []) {
|
if ($methods !== []) {
|
||||||
@ -165,21 +165,21 @@ final class TestCaseFactory
|
|||||||
$classFQN .= $className;
|
$classFQN .= $className;
|
||||||
}
|
}
|
||||||
|
|
||||||
$classAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute) => $attribute::ABOVE_CLASS);
|
$classAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => $attribute::$above);
|
||||||
$methodAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute) => ! $attribute::ABOVE_CLASS);
|
$methodAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => ! $attribute::$above);
|
||||||
|
|
||||||
$classAttributes = [];
|
$classAttributes = [];
|
||||||
|
|
||||||
foreach ($classAvailableAttributes as $attribute) {
|
foreach ($classAvailableAttributes as $attribute) {
|
||||||
$classAttributes = array_reduce(
|
$classAttributes = array_reduce(
|
||||||
$methods,
|
$methods,
|
||||||
fn (array $carry, TestCaseMethodFactory $methodFactory) => (new $attribute())->__invoke($methodFactory, $carry),
|
fn (array $carry, TestCaseMethodFactory $methodFactory): array => (new $attribute())->__invoke($methodFactory, $carry),
|
||||||
$classAttributes
|
$classAttributes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$methodsCode = implode('', array_map(
|
$methodsCode = implode('', array_map(
|
||||||
fn (TestCaseMethodFactory $methodFactory) => $methodFactory->buildForEvaluation(
|
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(
|
||||||
$classFQN,
|
$classFQN,
|
||||||
self::ANNOTATIONS,
|
self::ANNOTATIONS,
|
||||||
$methodAvailableAttributes
|
$methodAvailableAttributes
|
||||||
@ -188,7 +188,7 @@ final class TestCaseFactory
|
|||||||
));
|
));
|
||||||
|
|
||||||
$classAttributesCode = implode('', array_map(
|
$classAttributesCode = implode('', array_map(
|
||||||
static fn (string $attribute) => sprintf("\n%s", $attribute),
|
static fn (string $attribute): string => sprintf("\n%s", $attribute),
|
||||||
array_unique($classAttributes),
|
array_unique($classAttributes),
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ final class TestCaseFactory
|
|||||||
}
|
}
|
||||||
PHP;
|
PHP;
|
||||||
|
|
||||||
eval($classCode);
|
eval($classCode); // @phpstan-ignore-line
|
||||||
} catch (ParseError $caught) {
|
} catch (ParseError $caught) {
|
||||||
throw new RuntimeException(sprintf(
|
throw new RuntimeException(sprintf(
|
||||||
"Unable to create test case for test file at %s. \n %s",
|
"Unable to create test case for test file at %s. \n %s",
|
||||||
|
|||||||
@ -97,7 +97,7 @@ final class TestCaseMethodFactory
|
|||||||
$testCase->chains->chain($this);
|
$testCase->chains->chain($this);
|
||||||
$method->chains->chain($this);
|
$method->chains->chain($this);
|
||||||
|
|
||||||
return \Pest\Support\Closure::bind($closure, $this, $this::class)(...func_get_args());
|
return \Pest\Support\Closure::bind($closure, $this, self::class)(...func_get_args());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +123,9 @@ final class TestCaseMethodFactory
|
|||||||
|
|
||||||
$methodName = Str::evaluable($this->description);
|
$methodName = Str::evaluable($this->description);
|
||||||
|
|
||||||
if (Retry::$retrying && ! TestSuite::getInstance()->retryTempRepository->exists(sprintf('%s::%s', $classFQN, $methodName))) {
|
$retryRepository = TestSuite::getInstance()->retryRepository;
|
||||||
|
|
||||||
|
if (Retry::$retrying && ! $retryRepository->isEmpty() && ! $retryRepository->exists(sprintf('%s::%s', $classFQN, $methodName))) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,11 +149,11 @@ final class TestCaseMethodFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
$annotations = implode('', array_map(
|
$annotations = implode('', array_map(
|
||||||
static fn ($annotation) => sprintf("\n * %s", $annotation), $annotations,
|
static fn ($annotation): string => sprintf("\n * %s", $annotation), $annotations,
|
||||||
));
|
));
|
||||||
|
|
||||||
$attributes = implode('', array_map(
|
$attributes = implode('', array_map(
|
||||||
static fn ($attribute) => sprintf("\n %s", $attribute), $attributes,
|
static fn ($attribute): string => sprintf("\n %s", $attribute), $attributes,
|
||||||
));
|
));
|
||||||
|
|
||||||
return <<<PHP
|
return <<<PHP
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use Pest\PendingCalls\TestCall;
|
|||||||
use Pest\PendingCalls\UsesCall;
|
use Pest\PendingCalls\UsesCall;
|
||||||
use Pest\Repositories\DatasetsRepository;
|
use Pest\Repositories\DatasetsRepository;
|
||||||
use Pest\Support\Backtrace;
|
use Pest\Support\Backtrace;
|
||||||
|
use Pest\Support\DatasetInfo;
|
||||||
use Pest\Support\HigherOrderTapProxy;
|
use Pest\Support\HigherOrderTapProxy;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@ -60,7 +61,8 @@ if (! function_exists('dataset')) {
|
|||||||
*/
|
*/
|
||||||
function dataset(string $name, Closure|iterable $dataset): void
|
function dataset(string $name, Closure|iterable $dataset): void
|
||||||
{
|
{
|
||||||
DatasetsRepository::set($name, $dataset);
|
$scope = DatasetInfo::scope(Backtrace::datasetsFile());
|
||||||
|
DatasetsRepository::set($name, $dataset, $scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,9 +87,9 @@ if (! function_exists('test')) {
|
|||||||
* is the test description; the second argument is
|
* is the test description; the second argument is
|
||||||
* a closure that contains the test expectations.
|
* a closure that contains the test expectations.
|
||||||
*
|
*
|
||||||
* @return TestCall|TestCase|mixed
|
* @return HigherOrderTapProxy<TestCall|TestCase>|TestCall
|
||||||
*/
|
*/
|
||||||
function test(string $description = null, Closure $closure = null)
|
function test(string $description = null, Closure $closure = null): HigherOrderTapProxy|TestCall
|
||||||
{
|
{
|
||||||
if ($description === null && TestSuite::getInstance()->test !== null) {
|
if ($description === null && TestSuite::getInstance()->test !== null) {
|
||||||
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
||||||
@ -128,10 +130,11 @@ if (! function_exists('todo')) {
|
|||||||
*/
|
*/
|
||||||
function todo(string $description): TestCall
|
function todo(string $description): TestCall
|
||||||
{
|
{
|
||||||
/* @phpstan-ignore-next-line */
|
$test = test($description);
|
||||||
return test($description, fn () => self::markTestSkipped(
|
|
||||||
'__TODO__',
|
assert($test instanceof TestCall);
|
||||||
));
|
|
||||||
|
return $test->skip('__TODO__');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest;
|
namespace Pest;
|
||||||
|
|
||||||
|
use Pest\Contracts\Bootstrapper;
|
||||||
use Pest\Plugins\Actions\CallsAddsOutput;
|
use Pest\Plugins\Actions\CallsAddsOutput;
|
||||||
use Pest\Plugins\Actions\CallsBoot;
|
use Pest\Plugins\Actions\CallsBoot;
|
||||||
use Pest\Plugins\Actions\CallsShutdown;
|
use Pest\Plugins\Actions\CallsShutdown;
|
||||||
@ -22,6 +23,7 @@ final class Kernel
|
|||||||
* @var array<int, class-string>
|
* @var array<int, class-string>
|
||||||
*/
|
*/
|
||||||
private const BOOTSTRAPPERS = [
|
private const BOOTSTRAPPERS = [
|
||||||
|
Bootstrappers\BootOverrides::class,
|
||||||
Bootstrappers\BootExceptionHandler::class,
|
Bootstrappers\BootExceptionHandler::class,
|
||||||
Bootstrappers\BootSubscribers::class,
|
Bootstrappers\BootSubscribers::class,
|
||||||
Bootstrappers\BootFiles::class,
|
Bootstrappers\BootFiles::class,
|
||||||
@ -49,7 +51,10 @@ final class Kernel
|
|||||||
public static function boot(): self
|
public static function boot(): self
|
||||||
{
|
{
|
||||||
foreach (self::BOOTSTRAPPERS as $bootstrapper) {
|
foreach (self::BOOTSTRAPPERS as $bootstrapper) {
|
||||||
Container::getInstance()->get($bootstrapper)->__invoke();
|
$bootstrapper = Container::getInstance()->get($bootstrapper);
|
||||||
|
assert($bootstrapper instanceof Bootstrapper);
|
||||||
|
|
||||||
|
$bootstrapper->boot();
|
||||||
}
|
}
|
||||||
|
|
||||||
(new CallsBoot())->__invoke();
|
(new CallsBoot())->__invoke();
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
/*
|
|
||||||
* This file is part of PHPUnit.
|
|
||||||
*
|
|
||||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Pest\Logging;
|
|
||||||
|
|
||||||
use Pest\Support\Printer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*/
|
|
||||||
final class JUnit extends Printer
|
|
||||||
{
|
|
||||||
// @todo
|
|
||||||
}
|
|
||||||
@ -154,7 +154,7 @@ final class TestCall
|
|||||||
|
|
||||||
$condition = is_callable($condition)
|
$condition = is_callable($condition)
|
||||||
? $condition
|
? $condition
|
||||||
: fn () => $condition;
|
: fn (): bool => $condition;
|
||||||
|
|
||||||
$message = is_string($conditionOrMessage)
|
$message = is_string($conditionOrMessage)
|
||||||
? $conditionOrMessage
|
? $conditionOrMessage
|
||||||
@ -170,6 +170,16 @@ final class TestCall
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the test as "todo".
|
||||||
|
*/
|
||||||
|
public function todo(): self
|
||||||
|
{
|
||||||
|
$this->skip('__TODO__');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the covered classes or methods.
|
* Sets the covered classes or methods.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -23,7 +23,7 @@ final class Plugin
|
|||||||
public static function uses(string ...$traits): void
|
public static function uses(string ...$traits): void
|
||||||
{
|
{
|
||||||
self::$callables[] = function () use ($traits): void {
|
self::$callables[] = function () use ($traits): void {
|
||||||
uses(...$traits)->in(TestSuite::getInstance()->rootPath.DIRECTORY_SEPARATOR.testDirectory());
|
uses(...$traits)->in(TestSuite::getInstance()->rootPath);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ final class Memory implements AddsOutput, HandlesArguments
|
|||||||
{
|
{
|
||||||
if ($this->enabled) {
|
if ($this->enabled) {
|
||||||
$this->output->writeln(sprintf(
|
$this->output->writeln(sprintf(
|
||||||
' <fg=gray;options=bold>Memory:</> <fg=default>%s MB</>',
|
' <fg=gray>Memory:</> <fg=default>%s MB</>',
|
||||||
round(memory_get_usage(true) / 1000 ** 2, 3)
|
round(memory_get_usage(true) / 1000 ** 2, 3)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Repositories;
|
namespace Pest\Repositories;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Pest\Exceptions\DatasetAlreadyExist;
|
use Generator;
|
||||||
|
use Pest\Exceptions\DatasetAlreadyExists;
|
||||||
use Pest\Exceptions\DatasetDoesNotExist;
|
use Pest\Exceptions\DatasetDoesNotExist;
|
||||||
use Pest\Exceptions\ShouldNotHappen;
|
use Pest\Exceptions\ShouldNotHappen;
|
||||||
use SebastianBergmann\Exporter\Exporter;
|
use SebastianBergmann\Exporter\Exporter;
|
||||||
@ -17,6 +18,8 @@ use Traversable;
|
|||||||
*/
|
*/
|
||||||
final class DatasetsRepository
|
final class DatasetsRepository
|
||||||
{
|
{
|
||||||
|
private const SEPARATOR = '>>';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the datasets.
|
* Holds the datasets.
|
||||||
*
|
*
|
||||||
@ -36,13 +39,15 @@ final class DatasetsRepository
|
|||||||
*
|
*
|
||||||
* @param Closure|iterable<int|string, mixed> $data
|
* @param Closure|iterable<int|string, mixed> $data
|
||||||
*/
|
*/
|
||||||
public static function set(string $name, Closure|iterable $data): void
|
public static function set(string $name, Closure|iterable $data, string $scope): void
|
||||||
{
|
{
|
||||||
if (array_key_exists($name, self::$datasets)) {
|
$datasetKey = "$scope".self::SEPARATOR."$name";
|
||||||
throw new DatasetAlreadyExist($name);
|
|
||||||
|
if (array_key_exists("$datasetKey", self::$datasets)) {
|
||||||
|
throw new DatasetAlreadyExists($name, $scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$datasets[$name] = $data;
|
self::$datasets[$datasetKey] = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,12 +57,12 @@ final class DatasetsRepository
|
|||||||
*/
|
*/
|
||||||
public static function with(string $filename, string $description, array $with): void
|
public static function with(string $filename, string $description, array $with): void
|
||||||
{
|
{
|
||||||
self::$withs[$filename.'>>>'.$description] = $with;
|
self::$withs["$filename".self::SEPARATOR."$description"] = $with;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function has(string $filename, string $description): bool
|
public static function has(string $filename, string $description): bool
|
||||||
{
|
{
|
||||||
return array_key_exists($filename.'>>>'.$description, self::$withs);
|
return array_key_exists($filename.self::SEPARATOR.$description, self::$withs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,9 +72,9 @@ final class DatasetsRepository
|
|||||||
*/
|
*/
|
||||||
public static function get(string $filename, string $description)
|
public static function get(string $filename, string $description)
|
||||||
{
|
{
|
||||||
$dataset = self::$withs[$filename.'>>>'.$description];
|
$dataset = self::$withs[$filename.self::SEPARATOR.$description];
|
||||||
|
|
||||||
$dataset = self::resolve($description, $dataset);
|
$dataset = self::resolve($dataset, $filename);
|
||||||
|
|
||||||
if ($dataset === null) {
|
if ($dataset === null) {
|
||||||
throw ShouldNotHappen::fromMessage('Dataset [%s] not resolvable.');
|
throw ShouldNotHappen::fromMessage('Dataset [%s] not resolvable.');
|
||||||
@ -84,14 +89,13 @@ final class DatasetsRepository
|
|||||||
* @param array<Closure|iterable<int|string, mixed>|string> $dataset
|
* @param array<Closure|iterable<int|string, mixed>|string> $dataset
|
||||||
* @return array<string, mixed>|null
|
* @return array<string, mixed>|null
|
||||||
*/
|
*/
|
||||||
public static function resolve(string $description, array $dataset): array|null
|
public static function resolve(array $dataset, string $currentTestFile): array|null
|
||||||
{
|
{
|
||||||
/* @phpstan-ignore-next-line */
|
if ($dataset === []) {
|
||||||
if (empty($dataset)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$dataset = self::processDatasets($dataset);
|
$dataset = self::processDatasets($dataset, $currentTestFile);
|
||||||
|
|
||||||
$datasetCombinations = self::getDatasetsCombinations($dataset);
|
$datasetCombinations = self::getDatasetsCombinations($dataset);
|
||||||
|
|
||||||
@ -136,7 +140,7 @@ final class DatasetsRepository
|
|||||||
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
|
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
|
||||||
* @return array<array<mixed>>
|
* @return array<array<mixed>>
|
||||||
*/
|
*/
|
||||||
private static function processDatasets(array $datasets): array
|
private static function processDatasets(array $datasets, string $currentTestFile): array
|
||||||
{
|
{
|
||||||
$processedDatasets = [];
|
$processedDatasets = [];
|
||||||
|
|
||||||
@ -144,11 +148,7 @@ final class DatasetsRepository
|
|||||||
$processedDataset = [];
|
$processedDataset = [];
|
||||||
|
|
||||||
if (is_string($data)) {
|
if (is_string($data)) {
|
||||||
if (! array_key_exists($data, self::$datasets)) {
|
$datasets[$index] = self::getScopedDataset($data, $currentTestFile);
|
||||||
throw new DatasetDoesNotExist($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$datasets[$index] = self::$datasets[$data];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_callable($datasets[$index])) {
|
if (is_callable($datasets[$index])) {
|
||||||
@ -156,14 +156,16 @@ final class DatasetsRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($datasets[$index] instanceof Traversable) {
|
if ($datasets[$index] instanceof Traversable) {
|
||||||
$datasets[$index] = iterator_to_array($datasets[$index], false);
|
$preserveKeysForArrayIterator = $datasets[$index] instanceof Generator
|
||||||
|
&& is_string($datasets[$index]->key());
|
||||||
|
|
||||||
|
$datasets[$index] = iterator_to_array($datasets[$index], $preserveKeysForArrayIterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
foreach ($datasets[$index] as $key => $values) {
|
foreach ($datasets[$index] as $key => $values) {
|
||||||
$values = is_array($values) ? $values : [$values];
|
$values = is_array($values) ? $values : [$values];
|
||||||
$processedDataset[] = [
|
$processedDataset[] = [
|
||||||
'label' => self::getDatasetDescription($key, $values), // @phpstan-ignore-line
|
'label' => self::getDatasetDescription($key, $values),
|
||||||
'values' => $values,
|
'values' => $values,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -174,6 +176,33 @@ final class DatasetsRepository
|
|||||||
return $processedDatasets;
|
return $processedDatasets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Closure|iterable<int|string, mixed>
|
||||||
|
*/
|
||||||
|
private static function getScopedDataset(string $name, string $currentTestFile): Closure|iterable
|
||||||
|
{
|
||||||
|
$matchingDatasets = array_filter(self::$datasets, function (string $key) use ($name, $currentTestFile): bool {
|
||||||
|
[$datasetScope, $datasetName] = explode(self::SEPARATOR, $key);
|
||||||
|
|
||||||
|
if ($name !== $datasetName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_starts_with($currentTestFile, $datasetScope);
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
$closestScopeDatasetKey = array_reduce(
|
||||||
|
array_keys($matchingDatasets),
|
||||||
|
fn ($keyA, $keyB) => $keyA !== null && strlen((string) $keyA) > strlen($keyB) ? $keyA : $keyB
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($closestScopeDatasetKey === null) {
|
||||||
|
throw new DatasetDoesNotExist($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matchingDatasets[$closestScopeDatasetKey];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<array<mixed>> $combinations
|
* @param array<array<mixed>> $combinations
|
||||||
* @return array<array<array<mixed>>>
|
* @return array<array<array<mixed>>>
|
||||||
|
|||||||
@ -7,9 +7,15 @@ namespace Pest\Repositories;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class TempRepository
|
final class RetryRepository
|
||||||
{
|
{
|
||||||
private const FOLDER = __DIR__.'/../../.temp';
|
private const TEMPORARY_FOLDER = __DIR__
|
||||||
|
.DIRECTORY_SEPARATOR
|
||||||
|
.'..'
|
||||||
|
.DIRECTORY_SEPARATOR
|
||||||
|
.'..'
|
||||||
|
.DIRECTORY_SEPARATOR
|
||||||
|
.'.temp';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Temp Repository instance.
|
* Creates a new Temp Repository instance.
|
||||||
@ -32,11 +38,19 @@ final class TempRepository
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
@unlink(self::FOLDER.'/'.$this->filename.'.json'); // @phpstan-ignore-line
|
@unlink(self::TEMPORARY_FOLDER.'/'.$this->filename.'.json'); // @phpstan-ignore-line
|
||||||
|
|
||||||
$this->save([]);
|
$this->save([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there is any element.
|
||||||
|
*/
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
return $this->all() === [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given element exists.
|
* Checks if the given element exists.
|
||||||
*/
|
*/
|
||||||
@ -52,7 +66,9 @@ final class TempRepository
|
|||||||
*/
|
*/
|
||||||
private function all(): array
|
private function all(): array
|
||||||
{
|
{
|
||||||
$contents = file_get_contents(self::FOLDER.'/'.$this->filename.'.json');
|
$path = self::TEMPORARY_FOLDER.'/'.$this->filename.'.json';
|
||||||
|
|
||||||
|
$contents = file_exists($path) ? file_get_contents($path) : '{}';
|
||||||
|
|
||||||
assert(is_string($contents));
|
assert(is_string($contents));
|
||||||
|
|
||||||
@ -70,6 +86,6 @@ final class TempRepository
|
|||||||
{
|
{
|
||||||
$contents = json_encode($elements, JSON_THROW_ON_ERROR);
|
$contents = json_encode($elements, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
file_put_contents(self::FOLDER.'/'.$this->filename.'.json', $contents);
|
file_put_contents(self::TEMPORARY_FOLDER.'/'.$this->filename.'.json', $contents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ final class TestRepository
|
|||||||
*/
|
*/
|
||||||
public function getFilenames(): array
|
public function getFilenames(): array
|
||||||
{
|
{
|
||||||
$testCases = array_filter($this->testCases, static fn(TestCaseFactory $testCase) => $testCase->methodsUsingOnly() !== []);
|
$testCases = array_filter($this->testCases, static fn (TestCaseFactory $testCase): bool => $testCase->methodsUsingOnly() !== []);
|
||||||
|
|
||||||
if ($testCases === []) {
|
if ($testCases === []) {
|
||||||
$testCases = $this->testCases;
|
$testCases = $this->testCases;
|
||||||
|
|||||||
23
src/Subscribers/EnsureErroredTestsAreRetryable.php
Normal file
23
src/Subscribers/EnsureErroredTestsAreRetryable.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Subscribers;
|
||||||
|
|
||||||
|
use Pest\TestSuite;
|
||||||
|
use PHPUnit\Event\Test\Errored;
|
||||||
|
use PHPUnit\Event\Test\ErroredSubscriber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class EnsureErroredTestsAreRetryable implements ErroredSubscriber
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Runs the subscriber.
|
||||||
|
*/
|
||||||
|
public function notify(Errored $event): void
|
||||||
|
{
|
||||||
|
TestSuite::getInstance()->retryRepository->add($event->test()->id());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,13 +11,13 @@ use PHPUnit\Event\Test\FailedSubscriber;
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class EnsureFailedTestsAreStoredForRetry implements FailedSubscriber
|
final class EnsureFailedTestsAreRetryable implements FailedSubscriber
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Runs the subscriber.
|
* Runs the subscriber.
|
||||||
*/
|
*/
|
||||||
public function notify(Failed $event): void
|
public function notify(Failed $event): void
|
||||||
{
|
{
|
||||||
TestSuite::getInstance()->retryTempRepository->add($event->test()->id());
|
TestSuite::getInstance()->retryRepository->add($event->test()->id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,6 +18,6 @@ final class EnsureRetryRepositoryExists implements StartedSubscriber
|
|||||||
*/
|
*/
|
||||||
public function notify(Started $event): void
|
public function notify(Started $event): void
|
||||||
{
|
{
|
||||||
TestSuite::getInstance()->retryTempRepository->boot();
|
TestSuite::getInstance()->retryRepository->boot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,30 @@ final class Backtrace
|
|||||||
return $current[self::FILE];
|
return $current[self::FILE];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current datasets file.
|
||||||
|
*/
|
||||||
|
public static function datasetsFile(): string
|
||||||
|
{
|
||||||
|
$current = null;
|
||||||
|
|
||||||
|
foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) {
|
||||||
|
assert(array_key_exists(self::FILE, $trace));
|
||||||
|
|
||||||
|
if (Str::endsWith($trace['file'], 'Bootstrappers/BootFiles.php') || Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = $trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current === null) {
|
||||||
|
throw ShouldNotHappen::fromMessage('Dataset file not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $current[self::FILE];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the filename that called the current function/method.
|
* Returns the filename that called the current function/method.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -22,8 +22,8 @@ final class ChainableClosure
|
|||||||
throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.');
|
throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.');
|
||||||
}
|
}
|
||||||
|
|
||||||
\Pest\Support\Closure::bind($closure, $this, $this::class)(...func_get_args());
|
\Pest\Support\Closure::bind($closure, $this, self::class)(...func_get_args());
|
||||||
\Pest\Support\Closure::bind($next, $this, $this::class)(...func_get_args());
|
\Pest\Support\Closure::bind($next, $this, self::class)(...func_get_args());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ final class Container
|
|||||||
private static ?Container $instance = null;
|
private static ?Container $instance = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, mixed>
|
* @var array<string, object|string>
|
||||||
*/
|
*/
|
||||||
private array $instances = [];
|
private array $instances = [];
|
||||||
|
|
||||||
@ -25,37 +25,30 @@ final class Container
|
|||||||
*/
|
*/
|
||||||
public static function getInstance(): self
|
public static function getInstance(): self
|
||||||
{
|
{
|
||||||
if (static::$instance === null) {
|
if (self::$instance === null) {
|
||||||
static::$instance = new self();
|
self::$instance = new self();
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a dependency from the container.
|
* Gets a dependency from the container.
|
||||||
*
|
|
||||||
* @template TObject of object
|
|
||||||
*
|
|
||||||
* @param class-string<TObject> $id
|
|
||||||
* @return TObject
|
|
||||||
*/
|
*/
|
||||||
public function get(string $id): mixed
|
public function get(string $id): object|string
|
||||||
{
|
{
|
||||||
if (! array_key_exists($id, $this->instances)) {
|
if (! array_key_exists($id, $this->instances)) {
|
||||||
|
/** @var class-string $id */
|
||||||
$this->instances[$id] = $this->build($id);
|
$this->instances[$id] = $this->build($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var TObject $concrete */
|
return $this->instances[$id];
|
||||||
$concrete = $this->instances[$id];
|
|
||||||
|
|
||||||
return $concrete;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given instance to the container.
|
* Adds the given instance to the container.
|
||||||
*/
|
*/
|
||||||
public function add(string $id, mixed $instance): void
|
public function add(string $id, object|string $instance): void
|
||||||
{
|
{
|
||||||
$this->instances[$id] = $instance;
|
$this->instances[$id] = $instance;
|
||||||
}
|
}
|
||||||
@ -68,7 +61,7 @@ final class Container
|
|||||||
* @param class-string<TObject> $id
|
* @param class-string<TObject> $id
|
||||||
* @return TObject
|
* @return TObject
|
||||||
*/
|
*/
|
||||||
private function build(string $id): mixed
|
private function build(string $id): object
|
||||||
{
|
{
|
||||||
$reflectionClass = new ReflectionClass($id);
|
$reflectionClass = new ReflectionClass($id);
|
||||||
|
|
||||||
@ -77,7 +70,7 @@ final class Container
|
|||||||
|
|
||||||
if ($constructor !== null) {
|
if ($constructor !== null) {
|
||||||
$params = array_map(
|
$params = array_map(
|
||||||
function (ReflectionParameter $param) use ($id) {
|
function (ReflectionParameter $param) use ($id): object|string {
|
||||||
$candidate = Reflection::getParameterClassName($param);
|
$candidate = Reflection::getParameterClassName($param);
|
||||||
|
|
||||||
if ($candidate === null) {
|
if ($candidate === null) {
|
||||||
@ -90,7 +83,6 @@ final class Container
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
return $this->get($candidate);
|
return $this->get($candidate);
|
||||||
},
|
},
|
||||||
$constructor->getParameters()
|
$constructor->getParameters()
|
||||||
|
|||||||
@ -10,9 +10,9 @@ use SebastianBergmann\CodeCoverage\Node\Directory;
|
|||||||
use SebastianBergmann\CodeCoverage\Node\File;
|
use SebastianBergmann\CodeCoverage\Node\File;
|
||||||
use SebastianBergmann\Environment\Runtime;
|
use SebastianBergmann\Environment\Runtime;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Terminal;
|
|
||||||
use function Termwind\render;
|
use function Termwind\render;
|
||||||
use function Termwind\renderUsing;
|
use function Termwind\renderUsing;
|
||||||
|
use function Termwind\terminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -42,15 +42,15 @@ final class Coverage
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($runtime->hasXdebug()) {
|
if (! $runtime->hasXdebug()) {
|
||||||
if (version_compare((string) phpversion('xdebug'), '3.1', '>=')) {
|
return true;
|
||||||
if (! in_array('coverage', xdebug_info('mode'), true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (! version_compare((string) phpversion('xdebug'), '3.1', '>=')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array('coverage', xdebug_info('mode'), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,10 +83,6 @@ final class Coverage
|
|||||||
$codeCoverage = require $reportPath;
|
$codeCoverage = require $reportPath;
|
||||||
unlink($reportPath);
|
unlink($reportPath);
|
||||||
|
|
||||||
$totalWidth = (new Terminal())->getWidth();
|
|
||||||
|
|
||||||
$dottedLineLength = $totalWidth - 6;
|
|
||||||
|
|
||||||
$totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines();
|
$totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines();
|
||||||
|
|
||||||
/** @var Directory<File|Directory> $report */
|
/** @var Directory<File|Directory> $report */
|
||||||
@ -103,36 +99,23 @@ final class Coverage
|
|||||||
$dirname,
|
$dirname,
|
||||||
$basename,
|
$basename,
|
||||||
]);
|
]);
|
||||||
$rawName = $dirname === '.' ? $basename : implode(DIRECTORY_SEPARATOR, [
|
|
||||||
$dirname,
|
|
||||||
$basename,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$linesExecutedTakenSize = 0;
|
|
||||||
|
|
||||||
if ($file->percentageOfExecutedLines()->asString() != '0.00%') {
|
|
||||||
$linesExecutedTakenSize = strlen($uncoveredLines = trim(implode(', ', self::getMissingCoverage($file)))) + 1;
|
|
||||||
$name .= sprintf(' <fg=red>%s</>', $uncoveredLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
$percentage = $file->numberOfExecutableLines() === 0
|
$percentage = $file->numberOfExecutableLines() === 0
|
||||||
? '100.0'
|
? '100.0'
|
||||||
: number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', '');
|
: number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', '');
|
||||||
|
|
||||||
$takenSize = strlen($rawName.$percentage) + 2 + $linesExecutedTakenSize; // adding 3 space and percent sign
|
$color = $percentage === '100.0' ? 'green' : ($percentage === '0.0' ? 'red' : 'yellow');
|
||||||
|
|
||||||
$percentage = sprintf(
|
$truncateAt = max(1, terminal()->width() - 12);
|
||||||
'<fg=%s>%s</>',
|
|
||||||
$percentage === '100.0' ? 'green' : ($percentage === '0.0' ? 'red' : 'yellow'),
|
|
||||||
$percentage
|
|
||||||
);
|
|
||||||
|
|
||||||
$output->writeln(sprintf(
|
renderUsing($output);
|
||||||
' %s <fg=gray>%s</> %s <fg=gray>%%</>',
|
render(<<<HTML
|
||||||
$name,
|
<div class="flex mx-2">
|
||||||
str_repeat('.', max($dottedLineLength - $takenSize, 1)),
|
<span class="truncate-{$truncateAt}">{$name}</span>
|
||||||
$percentage
|
<span class="flex-1 content-repeat-[.] text-gray mx-1"></span>
|
||||||
));
|
<span class="text-{$color}">{$percentage}%</span>
|
||||||
|
</div>
|
||||||
|
HTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
$totalCoverageAsString = $totalCoverage->asFloat() === 0.0
|
$totalCoverageAsString = $totalCoverage->asFloat() === 0.0
|
||||||
|
|||||||
38
src/Support/DatasetInfo.php
Normal file
38
src/Support/DatasetInfo.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Support;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class DatasetInfo
|
||||||
|
{
|
||||||
|
public const DATASETS_DIR_NAME = 'Datasets';
|
||||||
|
|
||||||
|
public const DATASETS_FILE_NAME = 'Datasets.php';
|
||||||
|
|
||||||
|
public static function isInsideADatasetsDirectory(string $file): bool
|
||||||
|
{
|
||||||
|
return basename(dirname($file)) === self::DATASETS_DIR_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isADatasetsFile(string $file): bool
|
||||||
|
{
|
||||||
|
return basename($file) === self::DATASETS_FILE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function scope(string $file): string
|
||||||
|
{
|
||||||
|
if (self::isInsideADatasetsDirectory($file)) {
|
||||||
|
return dirname($file, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::isADatasetsFile($file)) {
|
||||||
|
return dirname($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,10 @@ use Throwable;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
*
|
||||||
|
* @template TProxy
|
||||||
|
*
|
||||||
|
* @mixin TProxy
|
||||||
*/
|
*/
|
||||||
final class HigherOrderTapProxy
|
final class HigherOrderTapProxy
|
||||||
{
|
{
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use ReflectionException;
|
|||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
use ReflectionNamedType;
|
use ReflectionNamedType;
|
||||||
use ReflectionParameter;
|
use ReflectionParameter;
|
||||||
|
use ReflectionProperty;
|
||||||
use ReflectionUnionType;
|
use ReflectionUnionType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,7 +96,7 @@ final class Reflection
|
|||||||
|
|
||||||
$reflectionProperty = null;
|
$reflectionProperty = null;
|
||||||
|
|
||||||
while ($reflectionProperty === null) {
|
while (! $reflectionProperty instanceof ReflectionProperty) {
|
||||||
try {
|
try {
|
||||||
/* @var ReflectionProperty $reflectionProperty */
|
/* @var ReflectionProperty $reflectionProperty */
|
||||||
$reflectionProperty = $reflectionClass->getProperty($property);
|
$reflectionProperty = $reflectionClass->getProperty($property);
|
||||||
@ -127,7 +128,7 @@ final class Reflection
|
|||||||
|
|
||||||
$reflectionProperty = null;
|
$reflectionProperty = null;
|
||||||
|
|
||||||
while ($reflectionProperty === null) {
|
while (! $reflectionProperty instanceof ReflectionProperty) {
|
||||||
try {
|
try {
|
||||||
/* @var ReflectionProperty $reflectionProperty */
|
/* @var ReflectionProperty $reflectionProperty */
|
||||||
$reflectionProperty = $reflectionClass->getProperty($property);
|
$reflectionProperty = $reflectionClass->getProperty($property);
|
||||||
|
|||||||
@ -56,7 +56,7 @@ final class Str
|
|||||||
{
|
{
|
||||||
$code = str_replace(' ', '_', $code);
|
$code = str_replace(' ', '_', $code);
|
||||||
|
|
||||||
return (string) preg_replace('/[^A-Z_a-z0-9\\\\]/', '', $code);
|
return (string) preg_replace('/[^A-Z_a-z0-9]/', '_', $code);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use Pest\Repositories\AfterAllRepository;
|
|||||||
use Pest\Repositories\AfterEachRepository;
|
use Pest\Repositories\AfterEachRepository;
|
||||||
use Pest\Repositories\BeforeAllRepository;
|
use Pest\Repositories\BeforeAllRepository;
|
||||||
use Pest\Repositories\BeforeEachRepository;
|
use Pest\Repositories\BeforeEachRepository;
|
||||||
use Pest\Repositories\TempRepository;
|
use Pest\Repositories\RetryRepository;
|
||||||
use Pest\Repositories\TestRepository;
|
use Pest\Repositories\TestRepository;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
@ -44,9 +44,9 @@ final class TestSuite
|
|||||||
public AfterAllRepository $afterAll;
|
public AfterAllRepository $afterAll;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the retry temp repository.
|
* Holds the retry repository.
|
||||||
*/
|
*/
|
||||||
public TempRepository $retryTempRepository;
|
public RetryRepository $retryRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the root path.
|
* Holds the root path.
|
||||||
@ -71,7 +71,7 @@ final class TestSuite
|
|||||||
$this->beforeEach = new BeforeEachRepository();
|
$this->beforeEach = new BeforeEachRepository();
|
||||||
$this->afterEach = new AfterEachRepository();
|
$this->afterEach = new AfterEachRepository();
|
||||||
$this->afterAll = new AfterAllRepository();
|
$this->afterAll = new AfterAllRepository();
|
||||||
$this->retryTempRepository = new TempRepository('retry');
|
$this->retryRepository = new RetryRepository('retry');
|
||||||
|
|
||||||
$this->rootPath = (string) realpath($rootPath);
|
$this->rootPath = (string) realpath($rootPath);
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ final class TestSuite
|
|||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self::$instance === null) {
|
if (! self::$instance instanceof self) {
|
||||||
throw new InvalidPestCommand();
|
throw new InvalidPestCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,9 +76,9 @@
|
|||||||
LOGGING OPTIONS:
|
LOGGING OPTIONS:
|
||||||
--log-junit <file> ................ Log test execution in JUnit XML format to file
|
--log-junit <file> ................ Log test execution in JUnit XML format to file
|
||||||
--log-teamcity <file> .............. Log test execution in TeamCity format to file
|
--log-teamcity <file> .............. Log test execution in TeamCity format to file
|
||||||
--testdox-html <file> ........... Write agile documentation in HTML format to file
|
--testdox-html <file> ................. Write documentation in HTML format to file
|
||||||
--testdox-text <file> ........... Write agile documentation in Text format to file
|
--testdox-text <file> ................. Write documentation in Text format to file
|
||||||
--testdox-xml <file> ............. Write agile documentation in XML format to file
|
--testdox-xml <file> ................... Write documentation in XML format to file
|
||||||
--log-events-text <file> ..................... Stream events as plain text to file
|
--log-events-text <file> ..................... Stream events as plain text to file
|
||||||
--log-events-verbose-text <file> Stream events as plain text to file (with telemetry information)
|
--log-events-verbose-text <file> Stream events as plain text to file (with telemetry information)
|
||||||
--no-logging .................................. Ignore logging configuration
|
--no-logging .................................. Ignore logging configuration
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
✓ it does not append CoversNothing to other methods
|
✓ it does not append CoversNothing to other methods
|
||||||
✓ it throws exception if no class nor method has been found
|
✓ it throws exception if no class nor method has been found
|
||||||
|
|
||||||
PASS Tests\Features\Datasets
|
PASS Tests\Features\DatasetsTests
|
||||||
✓ it throws exception if dataset does not exist
|
✓ it throws exception if dataset does not exist
|
||||||
✓ it throws exception if dataset already exist
|
✓ it throws exception if dataset already exist
|
||||||
✓ it sets closures
|
✓ it sets closures
|
||||||
@ -107,6 +107,8 @@
|
|||||||
✓ eager registered wrapped datasets with Generator functions with (3)
|
✓ eager registered wrapped datasets with Generator functions with (3)
|
||||||
✓ eager registered wrapped datasets with Generator functions with (4)
|
✓ eager registered wrapped datasets with Generator functions with (4)
|
||||||
✓ eager registered wrapped datasets with Generator functions did the job right
|
✓ eager registered wrapped datasets with Generator functions did the job right
|
||||||
|
✓ eager registered wrapped datasets with Generator functions display description with data set "taylor"
|
||||||
|
✓ eager registered wrapped datasets with Generator functions display description with data set "james"
|
||||||
✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #1
|
✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #1
|
||||||
✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #2
|
✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #2
|
||||||
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #1
|
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #1
|
||||||
@ -121,6 +123,7 @@
|
|||||||
✓ it will not resolve a closure if it is type hinted as a callable with (Closure Object (...)) #2
|
✓ it will not resolve a closure if it is type hinted as a callable with (Closure Object (...)) #2
|
||||||
✓ it can correctly resolve a bound dataset that returns an array with (Closure Object (...))
|
✓ it can correctly resolve a bound dataset that returns an array with (Closure Object (...))
|
||||||
✓ it can correctly resolve a bound dataset that returns an array but wants to be spread with (Closure Object (...))
|
✓ it can correctly resolve a bound dataset that returns an array but wants to be spread with (Closure Object (...))
|
||||||
|
↓ forbids to define tests in Datasets dirs and Datasets.php files
|
||||||
|
|
||||||
PASS Tests\Features\Depends
|
PASS Tests\Features\Depends
|
||||||
✓ first
|
✓ first
|
||||||
@ -668,6 +671,47 @@
|
|||||||
✓ get 'foo' → get 'bar' → expect true → toBeTrue
|
✓ get 'foo' → get 'bar' → expect true → toBeTrue
|
||||||
✓ get 'foo' → expect true → toBeTrue
|
✓ get 'foo' → expect true → toBeTrue
|
||||||
|
|
||||||
|
PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory1\TestFileInNestedDirectoryWithDatasetsFile
|
||||||
|
✓ uses dataset with (1)
|
||||||
|
✓ uses dataset with (2)
|
||||||
|
✓ uses dataset with (3)
|
||||||
|
✓ uses dataset with (4)
|
||||||
|
✓ uses dataset with (5)
|
||||||
|
✓ uses dataset with ('ScopedDatasets/NestedDirector...ts.php')
|
||||||
|
✓ the right dataset is taken
|
||||||
|
|
||||||
|
PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory2\TestFileInNestedDirectory
|
||||||
|
✓ uses dataset with (1)
|
||||||
|
✓ uses dataset with (2)
|
||||||
|
✓ uses dataset with (3)
|
||||||
|
✓ uses dataset with (4)
|
||||||
|
✓ uses dataset with (5)
|
||||||
|
✓ uses dataset with ('ScopedDatasets/Datasets/Scoped.php')
|
||||||
|
✓ the right dataset is taken
|
||||||
|
|
||||||
|
PASS Tests\Features\ScopedDatasets\Directory\TestFileWithLocallyDefinedDataset
|
||||||
|
✓ uses dataset with (1)
|
||||||
|
✓ uses dataset with (2)
|
||||||
|
✓ uses dataset with (3)
|
||||||
|
✓ uses dataset with (4)
|
||||||
|
✓ uses dataset with (5)
|
||||||
|
✓ uses dataset with ('ScopedDatasets/ScopedDatasets.php')
|
||||||
|
✓ the right dataset is taken
|
||||||
|
|
||||||
|
PASS Tests\Features\ScopedDatasets\Directory\TestFileWithScopedDataset
|
||||||
|
✓ uses dataset with (1)
|
||||||
|
✓ uses dataset with (2)
|
||||||
|
✓ uses dataset with (3)
|
||||||
|
✓ uses dataset with (4)
|
||||||
|
✓ uses dataset with (5)
|
||||||
|
✓ uses dataset with ('ScopedDatasets/Datasets/Scoped.php')
|
||||||
|
✓ the right dataset is taken
|
||||||
|
|
||||||
|
PASS Tests\Features\ScopedDatasets\TestFileOutOfScope
|
||||||
|
✓ uses dataset with (1)
|
||||||
|
✓ uses dataset with (2)
|
||||||
|
✓ the right dataset is taken
|
||||||
|
|
||||||
WARN Tests\Features\Skip
|
WARN Tests\Features\Skip
|
||||||
✓ it do not skips
|
✓ it do not skips
|
||||||
- it skips with truthy → 1
|
- it skips with truthy → 1
|
||||||
@ -690,6 +734,8 @@
|
|||||||
|
|
||||||
PASS Tests\Features\Todo
|
PASS Tests\Features\Todo
|
||||||
↓ something todo later
|
↓ something todo later
|
||||||
|
↓ something todo later chained
|
||||||
|
↓ something todo later chained and with function body
|
||||||
✓ it does something within a file with a todo
|
✓ it does something within a file with a todo
|
||||||
|
|
||||||
PASS Tests\Fixtures\DirectoryWithTests\ExampleTest
|
PASS Tests\Fixtures\DirectoryWithTests\ExampleTest
|
||||||
@ -713,29 +759,32 @@
|
|||||||
✓ global beforeEach execution order
|
✓ global beforeEach execution order
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\InvalidTestName
|
PASS Tests\PHPUnit\CustomAffixes\InvalidTestName
|
||||||
✓ it runs file names like `@#$%^&()-_=+.php`
|
✓ it runs file names like @#$%^&()-_=+.php
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\ATestWithSpaces
|
PASS Tests\PHPUnit\CustomAffixes\ATestWithSpaces
|
||||||
✓ it runs file names like `A Test With Spaces.php`
|
✓ it runs file names like A Test With Spaces.php
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\AdditionalFileExtension
|
PASS Tests\PHPUnit\CustomAffixes\AdditionalFileExtension
|
||||||
✓ it runs file names like `AdditionalFileExtension.spec.php`
|
✓ it runs file names like AdditionalFileExtension.spec.php
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\FolderWithAn\ExampleTest
|
PASS Tests\PHPUnit\CustomAffixes\FolderWithAn\ExampleTest
|
||||||
✓ custom traits can be used
|
✓ custom traits can be used
|
||||||
✓ trait applied in this file
|
✓ trait applied in this file
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\ManyExtensions
|
PASS Tests\PHPUnit\CustomAffixes\ManyExtensions
|
||||||
✓ it runs file names like `ManyExtensions.class.test.php`
|
✓ it runs file names like ManyExtensions.class.test.php
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\TestCaseWithQuotes
|
PASS Tests\PHPUnit\CustomAffixes\TestCaseWithQuotes
|
||||||
✓ it runs file names like `Test 'Case' With Quotes.php`
|
✓ it runs file names like Test 'Case' With Quotes.php
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\kebabcasespec
|
PASS Tests\PHPUnit\CustomAffixes\kebabcasespec
|
||||||
✓ it runs file names like `kebab-case-spec.php`
|
✓ it runs file names like kebab-case-spec.php
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomAffixes\snakecasespec
|
PASS Tests\PHPUnit\CustomAffixes\snakecasespec
|
||||||
✓ it runs file names like `snake_case_spec.php`
|
✓ it runs file names like snake_case_spec.php
|
||||||
|
|
||||||
|
PASS Tests\CustomTestCase\ExecutedTest
|
||||||
|
✓ that gets executed
|
||||||
|
|
||||||
PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory
|
PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory
|
||||||
✓ closure was bound to CustomTestCase
|
✓ closure was bound to CustomTestCase
|
||||||
@ -766,13 +815,17 @@
|
|||||||
PASS Tests\Unit\Console\Help
|
PASS Tests\Unit\Console\Help
|
||||||
✓ it outputs the help information when --help is used
|
✓ it outputs the help information when --help is used
|
||||||
|
|
||||||
PASS Tests\Unit\Datasets
|
PASS Tests\Unit\DatasetsTests
|
||||||
✓ it show only the names of named datasets in their description
|
✓ it show only the names of named datasets in their description
|
||||||
✓ it show the actual dataset of non-named datasets in their description
|
✓ it show the actual dataset of non-named datasets in their description
|
||||||
✓ it show only the names of multiple named datasets in their description
|
✓ it show only the names of multiple named datasets in their description
|
||||||
✓ it show the actual dataset of multiple non-named datasets in their description
|
✓ it show the actual dataset of multiple non-named datasets in their description
|
||||||
✓ it show the correct description for mixed named and not-named datasets
|
✓ it show the correct description for mixed named and not-named datasets
|
||||||
|
|
||||||
|
PASS Tests\Unit\Expectations\OppositeExpectation
|
||||||
|
✓ it throw expectation failed exception with string argument
|
||||||
|
✓ it throw expectation failed exception with array argument
|
||||||
|
|
||||||
PASS Tests\Unit\Plugins\Environment
|
PASS Tests\Unit\Plugins\Environment
|
||||||
✓ environment is set to CI when --ci option is used
|
✓ environment is set to CI when --ci option is used
|
||||||
✓ environment is set to Local when --ci option is not used
|
✓ environment is set to Local when --ci option is not used
|
||||||
@ -792,10 +845,35 @@
|
|||||||
✓ it can resolve builtin value types
|
✓ it can resolve builtin value types
|
||||||
✓ it cannot resolve a parameter without type
|
✓ it cannot resolve a parameter without type
|
||||||
|
|
||||||
|
PASS Tests\Unit\Support\DatasetInfo
|
||||||
|
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase...rs.php', true)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datasets.php', false)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur...rs.php', true)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur...rs.php', false)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur...ts.php', false)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Datase...rs.php', false)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Datasets.php', true)
|
||||||
|
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur...rs.php', false) #1
|
||||||
|
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur...rs.php', false) #2
|
||||||
|
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur...ts.php', true)
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Datase...rs.php', '/var/www/project/tests')
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Datasets.php', '/var/www/project/tests')
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Features')
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Featur...rs.php') #1
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Featur...ts.php', '/var/www/project/tests/Features')
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Featur...ollers')
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Featur...rs.php') #2
|
||||||
|
✓ it computes the dataset scope with ('/var/www/project/tests/Featur...ts.php', '/var/www/project/tests/Featur...ollers')
|
||||||
|
|
||||||
PASS Tests\Unit\Support\Reflection
|
PASS Tests\Unit\Support\Reflection
|
||||||
✓ it gets file name from closure
|
✓ it gets file name from closure
|
||||||
✓ it gets property values
|
✓ it gets property values
|
||||||
|
|
||||||
|
PASS Tests\Unit\Support\Str
|
||||||
|
✓ it evaluates the code with ('version()', 'version__')
|
||||||
|
✓ it evaluates the code with ('version__ ', 'version___')
|
||||||
|
✓ it evaluates the code with ('version\', 'version_')
|
||||||
|
|
||||||
PASS Tests\Unit\TestSuite
|
PASS Tests\Unit\TestSuite
|
||||||
✓ it does not allow to add the same test description twice
|
✓ it does not allow to add the same test description twice
|
||||||
✓ it alerts users about tests with arguments but no input
|
✓ it alerts users about tests with arguments but no input
|
||||||
@ -806,9 +884,6 @@
|
|||||||
PASS Tests\Visual\Help
|
PASS Tests\Visual\Help
|
||||||
✓ visual snapshot of help command output
|
✓ visual snapshot of help command output
|
||||||
|
|
||||||
WARN Tests\Visual\JUnit
|
|
||||||
- it is can successfully call all public methods → Not supported yet.
|
|
||||||
|
|
||||||
PASS Tests\Visual\SingleTestOrDirectory
|
PASS Tests\Visual\SingleTestOrDirectory
|
||||||
✓ allows to run a single test
|
✓ allows to run a single test
|
||||||
✓ allows to run a directory
|
✓ allows to run a directory
|
||||||
@ -823,4 +898,4 @@
|
|||||||
PASS Tests\Visual\Version
|
PASS Tests\Visual\Version
|
||||||
✓ visual snapshot of help command output
|
✓ visual snapshot of help command output
|
||||||
|
|
||||||
Tests: 4 incomplete, 1 todo, 18 skipped, 567 passed (1465 assertions)
|
Tests: 4 incomplete, 4 todos, 17 skipped, 624 passed (1511 assertions)
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Pest\Exceptions\DatasetAlreadyExist;
|
use Pest\Exceptions\DatasetAlreadyExists;
|
||||||
use Pest\Exceptions\DatasetDoesNotExist;
|
use Pest\Exceptions\DatasetDoesNotExist;
|
||||||
use Pest\Plugin;
|
use Pest\Plugin;
|
||||||
use Pest\Repositories\DatasetsRepository;
|
use Pest\Repositories\DatasetsRepository;
|
||||||
@ -13,28 +13,28 @@ it('throws exception if dataset does not exist', function () {
|
|||||||
$this->expectException(DatasetDoesNotExist::class);
|
$this->expectException(DatasetDoesNotExist::class);
|
||||||
$this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
$this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
||||||
|
|
||||||
DatasetsRepository::resolve('foo', ['first']);
|
DatasetsRepository::resolve(['first'], __FILE__);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws exception if dataset already exist', function () {
|
it('throws exception if dataset already exist', function () {
|
||||||
DatasetsRepository::set('second', [[]]);
|
DatasetsRepository::set('second', [[]], __DIR__);
|
||||||
$this->expectException(DatasetAlreadyExist::class);
|
$this->expectException(DatasetAlreadyExists::class);
|
||||||
$this->expectExceptionMessage('A dataset with the name `second` already exist.');
|
$this->expectExceptionMessage('A dataset with the name `second` already exist in scope ['.__DIR__.'].');
|
||||||
DatasetsRepository::set('second', [[]]);
|
DatasetsRepository::set('second', [[]], __DIR__);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets closures', function () {
|
it('sets closures', function () {
|
||||||
DatasetsRepository::set('foo', function () {
|
DatasetsRepository::set('foo', function () {
|
||||||
yield [1];
|
yield [1];
|
||||||
});
|
}, __DIR__);
|
||||||
|
|
||||||
expect(DatasetsRepository::resolve('foo', ['foo']))->toBe(['(1)' => [1]]);
|
expect(DatasetsRepository::resolve(['foo'], __FILE__))->toBe(['(1)' => [1]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets arrays', function () {
|
it('sets arrays', function () {
|
||||||
DatasetsRepository::set('bar', [[2]]);
|
DatasetsRepository::set('bar', [[2]], __DIR__);
|
||||||
|
|
||||||
expect(DatasetsRepository::resolve('bar', ['bar']))->toBe(['(2)' => [2]]);
|
expect(DatasetsRepository::resolve(['bar'], __FILE__))->toBe(['(2)' => [2]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets bound to test case object', function ($value) {
|
it('gets bound to test case object', function ($value) {
|
||||||
@ -249,6 +249,13 @@ test('eager registered wrapped datasets with Generator functions did the job rig
|
|||||||
expect($wrapped_generator_state->text)->toBe('1234');
|
expect($wrapped_generator_state->text)->toBe('1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('eager registered wrapped datasets with Generator functions display description', function ($wrapped_generator_state_with_description) {
|
||||||
|
expect($wrapped_generator_state_with_description)->not->toBeEmpty();
|
||||||
|
})->with(function () {
|
||||||
|
yield 'taylor' => 'taylor@laravel.com';
|
||||||
|
yield 'james' => 'james@laravel.com';
|
||||||
|
});
|
||||||
|
|
||||||
it('can resolve a dataset after the test case is available', function ($result) {
|
it('can resolve a dataset after the test case is available', function ($result) {
|
||||||
expect($result)->toBe('bar');
|
expect($result)->toBe('bar');
|
||||||
})->with([
|
})->with([
|
||||||
@ -323,3 +330,5 @@ it('can correctly resolve a bound dataset that returns an array but wants to be
|
|||||||
return ['foo', 'bar', 'baz'];
|
return ['foo', 'bar', 'baz'];
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
todo('forbids to define tests in Datasets dirs and Datasets.php files');
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
dataset('numbers.array', [
|
||||||
|
1, 2, 3, 4, 5, 'ScopedDatasets/Datasets/Scoped.php',
|
||||||
|
]);
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
dataset('numbers.array', [
|
||||||
|
1, 2, 3, 4, 5, 'ScopedDatasets/NestedDirectory1/Datasets.php',
|
||||||
|
]);
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$state = new stdClass();
|
||||||
|
$state->text = '';
|
||||||
|
test('uses dataset', function ($value) use ($state) {
|
||||||
|
$state->text .= $value;
|
||||||
|
expect(true)->toBe(true);
|
||||||
|
})->with('numbers.array');
|
||||||
|
|
||||||
|
test('the right dataset is taken', function () use ($state) {
|
||||||
|
expect($state->text)->toBe('12345ScopedDatasets/NestedDirectory1/Datasets.php');
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$state = new stdClass();
|
||||||
|
$state->text = '';
|
||||||
|
test('uses dataset', function ($value) use ($state) {
|
||||||
|
$state->text .= $value;
|
||||||
|
expect(true)->toBe(true);
|
||||||
|
})->with('numbers.array');
|
||||||
|
|
||||||
|
test('the right dataset is taken', function () use ($state) {
|
||||||
|
expect($state->text)->toBe('12345ScopedDatasets/Datasets/Scoped.php');
|
||||||
|
});
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
dataset('numbers.array', [
|
||||||
|
1, 2, 3, 4, 5, 'ScopedDatasets/ScopedDatasets.php',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$state = new stdClass();
|
||||||
|
$state->text = '';
|
||||||
|
test('uses dataset', function ($value) use ($state) {
|
||||||
|
$state->text .= $value;
|
||||||
|
expect(true)->toBe(true);
|
||||||
|
})->with('numbers.array');
|
||||||
|
|
||||||
|
test('the right dataset is taken', function () use ($state) {
|
||||||
|
expect($state->text)->toBe('12345ScopedDatasets/ScopedDatasets.php');
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$state = new stdClass();
|
||||||
|
$state->text = '';
|
||||||
|
test('uses dataset', function ($value) use ($state) {
|
||||||
|
$state->text .= $value;
|
||||||
|
expect(true)->toBe(true);
|
||||||
|
})->with('numbers.array');
|
||||||
|
|
||||||
|
test('the right dataset is taken', function () use ($state) {
|
||||||
|
expect($state->text)->toBe('12345ScopedDatasets/Datasets/Scoped.php');
|
||||||
|
});
|
||||||
12
tests/Features/ScopedDatasets/TestFileOutOfScope.php
Normal file
12
tests/Features/ScopedDatasets/TestFileOutOfScope.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$state = new stdClass();
|
||||||
|
$state->text = '';
|
||||||
|
test('uses dataset', function ($value) use ($state) {
|
||||||
|
$state->text .= $value;
|
||||||
|
expect(true)->toBe(true);
|
||||||
|
})->with('numbers.array');
|
||||||
|
|
||||||
|
test('the right dataset is taken', function () use ($state) {
|
||||||
|
expect($state->text)->toBe('12');
|
||||||
|
});
|
||||||
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
todo('something todo later');
|
todo('something todo later');
|
||||||
|
|
||||||
|
test('something todo later chained')->todo();
|
||||||
|
|
||||||
|
test('something todo later chained and with function body', function () {
|
||||||
|
expect(true)->toBeFalse();
|
||||||
|
})->todo();
|
||||||
|
|
||||||
it('does something within a file with a todo', function () {
|
it('does something within a file with a todo', function () {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,31 +3,31 @@
|
|||||||
use Pest\Repositories\DatasetsRepository;
|
use Pest\Repositories\DatasetsRepository;
|
||||||
|
|
||||||
it('show only the names of named datasets in their description', function () {
|
it('show only the names of named datasets in their description', function () {
|
||||||
$descriptions = array_keys(DatasetsRepository::resolve('test description', [
|
$descriptions = array_keys(DatasetsRepository::resolve([
|
||||||
[
|
[
|
||||||
'one' => [1],
|
'one' => [1],
|
||||||
'two' => [[2]],
|
'two' => [[2]],
|
||||||
],
|
],
|
||||||
]));
|
], __FILE__));
|
||||||
|
|
||||||
expect($descriptions[0])->toBe('data set "one"')
|
expect($descriptions[0])->toBe('data set "one"')
|
||||||
->and($descriptions[1])->toBe('data set "two"');
|
->and($descriptions[1])->toBe('data set "two"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('show the actual dataset of non-named datasets in their description', function () {
|
it('show the actual dataset of non-named datasets in their description', function () {
|
||||||
$descriptions = array_keys(DatasetsRepository::resolve('test description', [
|
$descriptions = array_keys(DatasetsRepository::resolve([
|
||||||
[
|
[
|
||||||
[1],
|
[1],
|
||||||
[[2]],
|
[[2]],
|
||||||
],
|
],
|
||||||
]));
|
], __FILE__));
|
||||||
|
|
||||||
expect($descriptions[0])->toBe('(1)');
|
expect($descriptions[0])->toBe('(1)');
|
||||||
expect($descriptions[1])->toBe('(array(2))');
|
expect($descriptions[1])->toBe('(array(2))');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('show only the names of multiple named datasets in their description', function () {
|
it('show only the names of multiple named datasets in their description', function () {
|
||||||
$descriptions = array_keys(DatasetsRepository::resolve('test description', [
|
$descriptions = array_keys(DatasetsRepository::resolve([
|
||||||
[
|
[
|
||||||
'one' => [1],
|
'one' => [1],
|
||||||
'two' => [[2]],
|
'two' => [[2]],
|
||||||
@ -36,7 +36,7 @@ it('show only the names of multiple named datasets in their description', functi
|
|||||||
'three' => [3],
|
'three' => [3],
|
||||||
'four' => [[4]],
|
'four' => [[4]],
|
||||||
],
|
],
|
||||||
]));
|
], __FILE__));
|
||||||
|
|
||||||
expect($descriptions[0])->toBe('data set "one" / data set "three"');
|
expect($descriptions[0])->toBe('data set "one" / data set "three"');
|
||||||
expect($descriptions[1])->toBe('data set "one" / data set "four"');
|
expect($descriptions[1])->toBe('data set "one" / data set "four"');
|
||||||
@ -45,7 +45,7 @@ it('show only the names of multiple named datasets in their description', functi
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('show the actual dataset of multiple non-named datasets in their description', function () {
|
it('show the actual dataset of multiple non-named datasets in their description', function () {
|
||||||
$descriptions = array_keys(DatasetsRepository::resolve('test description', [
|
$descriptions = array_keys(DatasetsRepository::resolve([
|
||||||
[
|
[
|
||||||
[1],
|
[1],
|
||||||
[[2]],
|
[[2]],
|
||||||
@ -54,7 +54,7 @@ it('show the actual dataset of multiple non-named datasets in their description'
|
|||||||
[3],
|
[3],
|
||||||
[[4]],
|
[[4]],
|
||||||
],
|
],
|
||||||
]));
|
], __FILE__));
|
||||||
|
|
||||||
expect($descriptions[0])->toBe('(1) / (3)');
|
expect($descriptions[0])->toBe('(1) / (3)');
|
||||||
expect($descriptions[1])->toBe('(1) / (array(4))');
|
expect($descriptions[1])->toBe('(1) / (array(4))');
|
||||||
@ -63,7 +63,7 @@ it('show the actual dataset of multiple non-named datasets in their description'
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('show the correct description for mixed named and not-named datasets', function () {
|
it('show the correct description for mixed named and not-named datasets', function () {
|
||||||
$descriptions = array_keys(DatasetsRepository::resolve('test description', [
|
$descriptions = array_keys(DatasetsRepository::resolve([
|
||||||
[
|
[
|
||||||
'one' => [1],
|
'one' => [1],
|
||||||
[[2]],
|
[[2]],
|
||||||
@ -72,7 +72,7 @@ it('show the correct description for mixed named and not-named datasets', functi
|
|||||||
[3],
|
[3],
|
||||||
'four' => [[4]],
|
'four' => [[4]],
|
||||||
],
|
],
|
||||||
]));
|
], __FILE__));
|
||||||
|
|
||||||
expect($descriptions[0])->toBe('data set "one" / (3)');
|
expect($descriptions[0])->toBe('data set "one" / (3)');
|
||||||
expect($descriptions[1])->toBe('data set "one" / data set "four"');
|
expect($descriptions[1])->toBe('data set "one" / data set "four"');
|
||||||
16
tests/Unit/Expectations/OppositeExpectation.php
Normal file
16
tests/Unit/Expectations/OppositeExpectation.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Pest\Expectations\OppositeExpectation;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
it('throw expectation failed exception with string argument', function (): void {
|
||||||
|
$expectation = new OppositeExpectation(expect('foo'));
|
||||||
|
|
||||||
|
$expectation->throwExpectationFailedException('toBe', 'bar');
|
||||||
|
})->throws(ExpectationFailedException::class, "Expecting 'foo' not to be 'bar'.");
|
||||||
|
|
||||||
|
it('throw expectation failed exception with array argument', function (): void {
|
||||||
|
$expectation = new OppositeExpectation(expect('foo'));
|
||||||
|
|
||||||
|
$expectation->throwExpectationFailedException('toBe', ['bar']);
|
||||||
|
})->throws(ExpectationFailedException::class, "Expecting 'foo' not to be 'bar'.");
|
||||||
36
tests/Unit/Support/DatasetInfo.php
Normal file
36
tests/Unit/Support/DatasetInfo.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Pest\Support\DatasetInfo;
|
||||||
|
|
||||||
|
it('can check if dataset is defined inside a Datasets directory', function (string $file, bool $inside) {
|
||||||
|
expect(DatasetInfo::isInsideADatasetsDirectory($file))->toBe($inside);
|
||||||
|
})->with([
|
||||||
|
['file' => '/var/www/project/tests/Datasets/Numbers.php', 'inside' => true],
|
||||||
|
['file' => '/var/www/project/tests/Datasets.php', 'inside' => false],
|
||||||
|
['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'inside' => true],
|
||||||
|
['file' => '/var/www/project/tests/Features/Numbers.php', 'inside' => false],
|
||||||
|
['file' => '/var/www/project/tests/Features/Datasets.php', 'inside' => false],
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('can check if dataset is defined inside a Datasets.php file', function (string $path, bool $inside) {
|
||||||
|
expect(DatasetInfo::isADatasetsFile($path))->toBe($inside);
|
||||||
|
})->with([
|
||||||
|
['file' => '/var/www/project/tests/Datasets/Numbers.php', 'inside' => false],
|
||||||
|
['file' => '/var/www/project/tests/Datasets.php', 'inside' => true],
|
||||||
|
['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'inside' => false],
|
||||||
|
['file' => '/var/www/project/tests/Features/Numbers.php', 'inside' => false],
|
||||||
|
['file' => '/var/www/project/tests/Features/Datasets.php', 'inside' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('computes the dataset scope', function (string $file, string $scope) {
|
||||||
|
expect(DatasetInfo::scope($file))->toBe($scope);
|
||||||
|
})->with([
|
||||||
|
['file' => '/var/www/project/tests/Datasets/Numbers.php', 'scope' => '/var/www/project/tests'],
|
||||||
|
['file' => '/var/www/project/tests/Datasets.php', 'scope' => '/var/www/project/tests'],
|
||||||
|
['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features'],
|
||||||
|
['file' => '/var/www/project/tests/Features/Numbers.php', 'scope' => '/var/www/project/tests/Features/Numbers.php'],
|
||||||
|
['file' => '/var/www/project/tests/Features/Datasets.php', 'scope' => '/var/www/project/tests/Features'],
|
||||||
|
['file' => '/var/www/project/tests/Features/Controllers/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers'],
|
||||||
|
['file' => '/var/www/project/tests/Features/Controllers/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers/Numbers.php'],
|
||||||
|
['file' => '/var/www/project/tests/Features/Controllers/Datasets.php', 'scope' => '/var/www/project/tests/Features/Controllers'],
|
||||||
|
]);
|
||||||
13
tests/Unit/Support/Str.php
Normal file
13
tests/Unit/Support/Str.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Pest\Support\Str;
|
||||||
|
|
||||||
|
it('evaluates the code', function ($evaluatable, $expected) {
|
||||||
|
$code = Str::evaluable($evaluatable);
|
||||||
|
|
||||||
|
expect($code)->toBe($expected);
|
||||||
|
})->with([
|
||||||
|
['version()', 'version__'],
|
||||||
|
['version__ ', 'version___'],
|
||||||
|
['version\\', 'version_'],
|
||||||
|
]);
|
||||||
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Pest\Logging\JUnit;
|
|
||||||
use PHPUnit\Framework\AssertionFailedError;
|
|
||||||
use PHPUnit\Framework\TestSuite;
|
|
||||||
use PHPUnit\Framework\Warning;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
file_put_contents(__DIR__.'/junit.html', '');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is can successfully call all public methods', function () {
|
|
||||||
$junit = new JUnit(__DIR__.'/junit.html');
|
|
||||||
$junit->startTestSuite(new TestSuite());
|
|
||||||
$junit->startTest($this);
|
|
||||||
$junit->addError($this, new Exception(), 0);
|
|
||||||
$junit->addFailure($this, new AssertionFailedError(), 0);
|
|
||||||
$junit->addWarning($this, new Warning(), 0);
|
|
||||||
$junit->addIncompleteTest($this, new Exception(), 0);
|
|
||||||
$junit->addRiskyTest($this, new Exception(), 0);
|
|
||||||
$junit->addSkippedTest($this, new Exception(), 0);
|
|
||||||
$junit->endTest($this, 0);
|
|
||||||
$junit->endTestSuite(new TestSuite());
|
|
||||||
$this->expectNotToPerformAssertions();
|
|
||||||
})->skip('Not supported yet.');
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
unlink(__DIR__.'/junit.html');
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user