Compare commits

...

73 Commits

Author SHA1 Message Date
eaeb133c77 release: v3.5.0 2024-10-22 15:33:27 +01:00
cf57ea1f94 Merge pull request #1295 from jshayes/nested-describe
Support for nested describe blocks
2024-10-22 13:41:38 +01:00
0b7f4f2384 Merge pull request #1301 from faissaloux/update-tests-badge
Update tests badge to v3
2024-10-20 12:48:07 +01:00
2903a7e621 release: v3.4.2 2024-10-20 12:47:25 +01:00
b8964375c7 release: v3.4.1 2024-10-20 12:43:35 +01:00
bdcb883829 chore: bumps phpunit version 2024-10-20 12:43:28 +01:00
8a7e7f39ef update tests badge to v3 2024-10-17 02:33:25 +01:00
67f217852c chore: uses stable version of collision and termwind 2024-10-15 17:17:09 +01:00
1bad148487 release: v3.4.0 2024-10-15 15:13:19 +01:00
e24f137b8e fix: deprecation 2024-10-15 15:09:26 +01:00
6d9189f3f5 feat: php 8.4 support 2024-10-15 15:04:30 +01:00
6968094e2b Add tests 2024-10-13 10:39:17 -04:00
9510d4a2f9 Change array type hint 2024-10-13 09:54:19 -04:00
cd2eb3504b Add helper to get last element of array 2024-10-13 09:47:54 -04:00
7c639cdbbd Add more tests 2024-10-13 00:41:04 -04:00
1513ede73b release: v3.3.2 2024-10-12 12:36:44 +01:00
8c65197881 chore: bumps depndencies 2024-10-12 12:33:57 +01:00
a6cd83665c Execute all parent beforeEach and afterEach functions for each test 2024-10-11 23:51:18 -04:00
0c57142c03 Fix an issue where a describe block will prevent a beforeEach call from executing 2024-10-11 21:24:08 -04:00
3f65af9fdf Merge pull request #1292 from olivernybroe/policy-suffix
Add Policy suffix to policies
2024-10-11 09:34:05 +01:00
42d89814e3 Merge pull request #1293 from AbdellahBoutmad/dir
modify test command in contrbuting.md
2024-10-11 09:33:41 +01:00
1e3156a5b6 release: v3.3.1 2024-10-11 09:31:24 +01:00
97713c0832 chore: bumps dependencies 2024-10-11 09:31:16 +01:00
62b0e3c9df modify test command in contrbuting.md 2024-10-10 12:34:04 +01:00
647de2f1cf Add Policy suffix to policies 2024-10-10 08:08:10 +02:00
0a7bff0d24 release: v3.3.0 2024-10-06 19:25:27 +01:00
7618434580 chore: uses phpunit v11.4.0 2024-10-06 19:25:20 +01:00
1e0bb88b73 release: v3.2.5 2024-10-01 11:55:18 +01:00
83b76d7c2e chore: bumps dependencies 2024-10-01 11:51:26 +01:00
5a870b3940 chore: style changes 2024-10-01 11:51:16 +01:00
1115c64186 chore: style changes 2024-10-01 11:48:14 +01:00
e38a271ca2 Merge pull request #1279 from midnite81/bug/declare-strict-types-with-comments-above
Strict types expectation allows for comments above declaration
2024-09-29 19:12:06 +01:00
43703ab40a Merge pull request #1280 from CamKem/fix/middleware-method-preset
Fix: Add middleware to the allowable public methods for Laravel Preset
2024-09-29 19:11:40 +01:00
86452765a4 fix: add middleware to the allowable public methods on the laravel preset 2024-09-29 16:07:39 +10:00
b8a1b7e5cc Add tests for strict types expectation
Introduced new test cases to ensure strict type declaration handling. Files with and without strict types are tested, including scenarios with comments preceding the declaration. Updated the regex in `Expectation.php` to accommodate comments and whitespaces before the `declare(strict_types=1)` statement.
2024-09-28 17:31:33 +01:00
5fe79d9c18 release: v3.2.4 2024-09-26 23:53:39 +01:00
2744da4292 Merge pull request #1277 from MuhammedAlkhudiry/ignore-handler-in-laravel-preset
ignore App\Exceptions\Handler.php in arch laravel preset
2024-09-26 23:47:26 +01:00
87f4e5e7b3 fix 2024-09-26 16:44:30 +03:00
bb3decf3cc ignore App\Exceptions\Handler.php in arch laravel preset 2024-09-26 16:41:43 +03:00
4e2987d438 release: v3.2.3 2024-09-25 16:19:39 +01:00
a25158bce8 Merge pull request #1275 from jeremynikolic/laravel-presets-ignore-concerns
feat: add ignoring of Concerns folder inside App\Enums and App\Features
2024-09-25 16:16:26 +01:00
49e77b1d4c feat: add ignoring of Concerns folder inside App\Enums and App\Features 2024-09-25 17:12:42 +02:00
989e43d1a0 release: v3.2.2 2024-09-24 10:23:43 +01:00
7cd42aafd8 fix: auto-complete on presets 2024-09-24 10:23:32 +01:00
48a1de273f release: v3.2.1 2024-09-23 14:09:55 +01:00
970e16e949 Ignores 2024-09-23 14:08:30 +01:00
432ff221c6 fix: missing != and !== on new toUseStrictEquality arch expectation 2024-09-23 14:08:21 +01:00
a55da85dd2 release: v3.2.0 2024-09-23 13:14:03 +01:00
f291cd1603 chore: bumps dependencies 2024-09-23 13:11:49 +01:00
5de0c2254a release: v3.1.0 2024-09-19 23:39:07 +01:00
b98ce0ced3 feat: adds mutates 2024-09-19 23:32:28 +01:00
28772c2609 chore: dont run integration tests yet on php 8.4 2024-09-19 13:42:01 +01:00
452ffaf8df chore: fixes windows build 2024-09-19 13:38:35 +01:00
e8338405b5 chore: tests against PHP 8.4 2024-09-19 13:36:41 +01:00
1b014e4b18 release: v3.0.8 2024-09-19 13:04:42 +01:00
034715e8b1 Merge pull request #1266 from julien-boudry/3.x
Fix #1265 -  issue parameter cannot be int (one done, pr, todo, wip)
2024-09-19 12:53:34 +01:00
09eff785c4 release: v3.0.7 2024-09-19 12:29:38 +01:00
22cc7805d7 chore: bumps dependencies 2024-09-19 12:29:38 +01:00
669dc0da71 Fix #1265 - issue parameter cannot be int (one done, pr, todo, wip) 2024-09-19 09:49:36 +00:00
689da4ed4e Merge pull request #1254 from pestphp/bugfix/jira-url
fix: update assignee URL for Jira
2024-09-18 21:48:09 +01:00
2f15861b0d fix: update assignee URL for Jira 2024-09-16 12:18:21 +01:00
0d50d35b5e release: v3.0.6 2024-09-11 18:59:43 +01:00
ce61ced8e1 Merge pull request #1237 from smirok/teamcity-fix-for-tests-with-dataset
fix: unify the `locationHint` prefix and prettify both `locationHint` and `name` parameters for testing with datasets
2024-09-11 18:51:04 +01:00
7227d24611 fix: unify the locationHint prefix and prettify both locationHint and name parameters for testing with datasets 2024-09-11 16:42:06 +02:00
45f16484d5 Merge pull request #1235 from pestphp/3.x_herd_fix
Fixes parallel mutation testing when using Laravel Herd
2024-09-11 15:13:49 +01:00
b16e8650da Fixes parallel mutation testing when using Laravel Herd. 2024-09-11 15:11:47 +01:00
c2f30e0148 Fixes parallel mutation testing when using Laravel Herd. 2024-09-11 15:04:44 +01:00
47ce45de56 release: v3.0.4 2024-09-11 00:48:29 +01:00
32881774d2 fix: global afterEach being called twice 2024-09-11 00:40:41 +01:00
ea72461f1b release: v3.0.3 2024-09-10 22:29:09 +01:00
49f15521e0 fix: printer method name 2024-09-10 22:29:01 +01:00
95c5394b66 Bumps dependencies 2024-09-10 16:59:38 +01:00
8de30cc8b7 release: v3.0.1 2024-09-09 15:29:44 +01:00
64 changed files with 1271 additions and 135 deletions

View File

@ -13,8 +13,8 @@ jobs:
fail-fast: true fail-fast: true
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
symfony: ['7.0'] symfony: ['7.1']
php: ['8.2', '8.3'] php: ['8.2', '8.3', '8.4']
dependency_version: [prefer-lowest, prefer-stable] dependency_version: [prefer-lowest, prefer-stable]
name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }}
@ -36,7 +36,8 @@ jobs:
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install PHP dependencies - name: Install PHP dependencies
run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:~${{ matrix.symfony }}" shell: bash
run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:^${{ matrix.symfony }}"
- name: Unit Tests - name: Unit Tests
run: composer test:unit run: composer test:unit

View File

@ -42,7 +42,7 @@ composer test
Check types: Check types:
```bash ```bash
composer test:types composer test:type:check
``` ```
Unit tests: Unit tests:

View File

@ -1,7 +1,7 @@
<p align="center"> <p align="center">
<img src="https://raw.githubusercontent.com/pestphp/art/master/v3/banner.png" width="600" alt="PEST"> <img src="https://raw.githubusercontent.com/pestphp/art/master/v3/banner.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/actions/workflow/status/pestphp/pest/tests.yml?branch=2.x&label=Tests%202.x"></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=3.x&label=Tests%203.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>

View File

@ -1,5 +1,7 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php declare(strict_types=1); <?php
declare(strict_types=1);
use Pest\Kernel; use Pest\Kernel;
use Pest\Panic; use Pest\Panic;
@ -37,7 +39,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
if (str_contains($value, '--test-directory=')) { if (str_contains($value, '--test-directory=')) {
unset($arguments[$key]); unset($arguments[$key]);
} else if ($value === '--test-directory') { } elseif ($value === '--test-directory') {
unset($arguments[$key]); unset($arguments[$key]);
if (isset($arguments[$key + 1])) { if (isset($arguments[$key + 1])) {
@ -62,7 +64,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
if (str_contains($value, '--assignee=')) { if (str_contains($value, '--assignee=')) {
unset($arguments[$key]); unset($arguments[$key]);
} else if ($value === '--assignee') { } elseif ($value === '--assignee') {
unset($arguments[$key]); unset($arguments[$key]);
if (isset($arguments[$key + 1])) { if (isset($arguments[$key + 1])) {
@ -72,7 +74,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
if (str_contains($value, '--issue=')) { if (str_contains($value, '--issue=')) {
unset($arguments[$key]); unset($arguments[$key]);
} else if ($value === '--issue') { } elseif ($value === '--issue') {
unset($arguments[$key]); unset($arguments[$key]);
if (isset($arguments[$key + 1])) { if (isset($arguments[$key + 1])) {
@ -82,7 +84,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
if (str_contains($value, '--ticket=')) { if (str_contains($value, '--ticket=')) {
unset($arguments[$key]); unset($arguments[$key]);
} else if ($value === '--ticket') { } elseif ($value === '--ticket') {
unset($arguments[$key]); unset($arguments[$key]);
if (isset($arguments[$key + 1])) { if (isset($arguments[$key + 1])) {
@ -92,7 +94,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
if (str_contains($value, '--pr=')) { if (str_contains($value, '--pr=')) {
unset($arguments[$key]); unset($arguments[$key]);
} else if ($value === '--pr') { } elseif ($value === '--pr') {
unset($arguments[$key]); unset($arguments[$key]);
if (isset($arguments[$key + 1])) { if (isset($arguments[$key + 1])) {
@ -102,7 +104,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
if (str_contains($value, '--pull-request=')) { if (str_contains($value, '--pull-request=')) {
unset($arguments[$key]); unset($arguments[$key]);
} else if ($value === '--pull-request') { } elseif ($value === '--pull-request') {
unset($arguments[$key]); unset($arguments[$key]);
if (isset($arguments[$key + 1])) { if (isset($arguments[$key + 1])) {
@ -117,7 +119,6 @@ use Symfony\Component\Console\Output\ConsoleOutput;
} }
} }
// Used when Pest is required using composer. // Used when Pest is required using composer.
$vendorPath = dirname(__DIR__, 4).'/vendor/autoload.php'; $vendorPath = dirname(__DIR__, 4).'/vendor/autoload.php';
@ -134,7 +135,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
// Get $rootPath based on $autoloadPath // Get $rootPath based on $autoloadPath
$rootPath = dirname($autoloadPath, 2); $rootPath = dirname($autoloadPath, 2);
$input = new ArgvInput(); $input = new ArgvInput;
$testSuite = TestSuite::getInstance( $testSuite = TestSuite::getInstance(
$rootPath, $rootPath,
@ -146,11 +147,11 @@ use Symfony\Component\Console\Output\ConsoleOutput;
} }
if ($todo) { if ($todo) {
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter()); $testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter);
} }
if ($notes) { if ($notes) {
$testSuite->tests->addTestCaseMethodFilter(new NotesTestCaseFilter()); $testSuite->tests->addTestCaseMethodFilter(new NotesTestCaseFilter);
} }
if ($assignee = $input->getParameterOption('--assignee')) { if ($assignee = $input->getParameterOption('--assignee')) {

View File

@ -18,16 +18,17 @@
], ],
"require": { "require": {
"php": "^8.2.0", "php": "^8.2.0",
"brianium/paratest": "^7.5.4", "brianium/paratest": "^7.6.0",
"nunomaduro/collision": "^8.4.0", "nunomaduro/collision": "^8.5.0",
"nunomaduro/termwind": "^2.1.0", "nunomaduro/termwind": "^2.2.0",
"pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin": "^3.0.0",
"pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0",
"pestphp/pest-plugin-mutate": "^3.0.1", "pestphp/pest-plugin-mutate": "^3.0.5",
"phpunit/phpunit": "^11.3.4" "phpunit/phpunit": "^11.4.2"
}, },
"conflict": { "conflict": {
"phpunit/phpunit": ">11.3.4", "filp/whoops": "<2.16.0",
"phpunit/phpunit": ">11.4.2",
"sebastian/exporter": "<6.0.0", "sebastian/exporter": "<6.0.0",
"webmozart/assert": "<1.11.0" "webmozart/assert": "<1.11.0"
}, },
@ -52,9 +53,9 @@
] ]
}, },
"require-dev": { "require-dev": {
"pestphp/pest-dev-tools": "^3.0.0", "pestphp/pest-dev-tools": "^3.3.0",
"pestphp/pest-plugin-type-coverage": "^3.0.0", "pestphp/pest-plugin-type-coverage": "^3.1.0",
"symfony/process": "^7.1.3" "symfony/process": "^7.1.5"
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true, "prefer-stable": true,

View File

@ -1,6 +1,5 @@
includes: includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/ergebnis/phpstan-rules/rules.neon
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon - vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
parameters: parameters:
@ -12,12 +11,4 @@ parameters:
reportUnmatchedIgnoredErrors: true reportUnmatchedIgnoredErrors: true
ignoreErrors: ignoreErrors:
- "#has a nullable return type declaration.#"
- "#Language construct isset\\(\\) should not be used.#"
- "#is not allowed to extend#"
- "#is concrete, but does not have a Test suffix#"
- "#with a nullable type declaration#"
- "#type mixed is not subtype of native#" - "#type mixed is not subtype of native#"
- "# with null as default value#"
- "#has parameter \\$closure with default value.#"
- "#has parameter \\$description with default value.#"

View File

@ -27,17 +27,20 @@ final class Laravel extends AbstractPreset
->ignoring('App\Enums'); ->ignoring('App\Enums');
$this->expectations[] = expect('App\Enums') $this->expectations[] = expect('App\Enums')
->toBeEnums(); ->toBeEnums()
->ignoring('App\Enums\Concerns');
$this->expectations[] = expect('App\Features') $this->expectations[] = expect('App\Features')
->toBeClasses(); ->toBeClasses()
->ignoring('App\Features\Concerns');
$this->expectations[] = expect('App\Features') $this->expectations[] = expect('App\Features')
->toHaveMethod('resolve'); ->toHaveMethod('resolve');
$this->expectations[] = expect('App\Exceptions') $this->expectations[] = expect('App\Exceptions')
->classes() ->classes()
->toImplement('Throwable'); ->toImplement('Throwable')
->ignoring('App\Exceptions\Handler');
$this->expectations[] = expect('App') $this->expectations[] = expect('App')
->not->toImplement(Throwable::class) ->not->toImplement(Throwable::class)
@ -149,7 +152,7 @@ final class Laravel extends AbstractPreset
->toOnlyBeUsedIn('App\Http'); ->toOnlyBeUsedIn('App\Http');
$this->expectations[] = expect('App\Http\Controllers') $this->expectations[] = expect('App\Http\Controllers')
->not->toHavePublicMethodsBesides(['__construct', '__invoke', 'index', 'show', 'create', 'store', 'edit', 'update', 'destroy']); ->not->toHavePublicMethodsBesides(['__construct', '__invoke', 'index', 'show', 'create', 'store', 'edit', 'update', 'destroy', 'middleware']);
$this->expectations[] = expect([ $this->expectations[] = expect([
'dd', 'dd',
@ -159,5 +162,9 @@ final class Laravel extends AbstractPreset
'exit', 'exit',
'ray', 'ray',
])->not->toBeUsed(); ])->not->toBeUsed();
$this->expectations[] = expect('App\Policies')
->classes()
->toHaveSuffix('Policy');
} }
} }

View File

@ -21,6 +21,7 @@ final class Strict extends AbstractPreset
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHaveProtectedMethods(), fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHaveProtectedMethods(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeAbstract(), fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeAbstract(),
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictTypes(), fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictTypes(),
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictEquality(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->toBeFinal(), fn (Expectation $namespace): ArchExpectation => $namespace->classes()->toBeFinal(),
); );

View File

@ -18,14 +18,14 @@ final class BootOverrides implements Bootstrapper
* @var array<string, string> * @var array<string, string>
*/ */
public const FILES = [ public const FILES = [
'c96b1cb57d7fc8e649f4c13a8abe418c2541bcfab194fb6702b99f777f52ee84' => 'Runner/Filter/NameFilterIterator.php', '53c246e5f416a39817ac81124cdd64ea8403038d01d7a202e1ffa486fbdf3fa7' => 'Runner/Filter/NameFilterIterator.php',
'a4a43de01f641c6944ee83d963795a46d32b5206b5ab3bbc6cce76e67190acbf' => 'Runner/ResultCache/DefaultResultCache.php', 'a4a43de01f641c6944ee83d963795a46d32b5206b5ab3bbc6cce76e67190acbf' => 'Runner/ResultCache/DefaultResultCache.php',
'd0e81317889ad88c707db4b08a94cadee4c9010d05ff0a759f04e71af5efed89' => 'Runner/TestSuiteLoader.php', 'd0e81317889ad88c707db4b08a94cadee4c9010d05ff0a759f04e71af5efed89' => 'Runner/TestSuiteLoader.php',
'3bb609b0d3bf6dee8df8d6cd62a3c8ece823c4bb941eaaae39e3cb267171b9d2' => 'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php', '3bb609b0d3bf6dee8df8d6cd62a3c8ece823c4bb941eaaae39e3cb267171b9d2' => 'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
'8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php', '8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
'43883b7e5811886cf3731c8ed6304d5a77078d9731e1e505abc2da36bde19f3e' => 'TextUI/TestSuiteFilterProcessor.php', 'b4250fc3ffad5954624cb5e682fd940b874e8d3422fa1ee298bd7225e1aa5fc2' => 'TextUI/TestSuiteFilterProcessor.php',
'357d5cd7007f8559b26e1b8cdf43bb6fb15b51b79db981779da6f31b7ec39dad' => 'Event/Value/ThrowableBuilder.php', '357d5cd7007f8559b26e1b8cdf43bb6fb15b51b79db981779da6f31b7ec39dad' => 'Event/Value/ThrowableBuilder.php',
'01974a686eba69b5fbb87a904d936eae2176e39567616898c5b758db71d87a22' => 'Logging/JUnit/JunitXmlLogger.php', '676273f1fe483877cf2d95c5aedbf9ae5d6a8e2f4c12d6ce716df6591e6db023' => 'Logging/JUnit/JunitXmlLogger.php',
]; ];
/** /**

View File

@ -61,8 +61,10 @@ trait Testable
/** /**
* The test's describing, if any. * The test's describing, if any.
*
* @var array<int, string>
*/ */
public ?string $__describing = null; public array $__describing = [];
/** /**
* Whether the test has ran or not. * Whether the test has ran or not.
@ -116,7 +118,7 @@ trait Testable
self::$__latestIssues = $method->issues; self::$__latestIssues = $method->issues;
self::$__latestPrs = $method->prs; self::$__latestPrs = $method->prs;
$this->__describing = $method->describing; $this->__describing = $method->describing;
$this->__test = $method->getClosure($this); $this->__test = $method->getClosure();
} }
} }
@ -240,6 +242,8 @@ trait Testable
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
$method->setUp($this);
$description = $method->description; $description = $method->description;
if ($this->dataName()) { if ($this->dataName()) {
$description = str_contains((string) $description, ':dataset') $description = str_contains((string) $description, ':dataset')
@ -298,6 +302,9 @@ trait Testable
parent::tearDown(); parent::tearDown();
TestSuite::getInstance()->test = null; TestSuite::getInstance()->test = null;
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
$method->tearDown($this);
} }
} }
@ -392,11 +399,12 @@ trait Testable
fn (ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(), fn (ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(),
array_filter($testReflection->getParameters(), fn (ReflectionParameter $reflectionParameter): bool => ! $reflectionParameter->isOptional()), array_filter($testReflection->getParameters(), fn (ReflectionParameter $reflectionParameter): bool => ! $reflectionParameter->isOptional()),
); );
if (array_diff($testParameterNames, $datasetParameterNames) === []) { if (array_diff($testParameterNames, $datasetParameterNames) === []) {
return; return;
} }
if (isset($testParameterNames[0])
&& $suppliedParametersCount >= $requiredParametersCount) { if (isset($testParameterNames[0]) && $suppliedParametersCount >= $requiredParametersCount) {
return; return;
} }

View File

@ -79,11 +79,11 @@ final readonly class Configuration
} }
/** /**
* Gets the theme configuration. * Gets the printer configuration.
*/ */
public function theme(): Configuration\Theme public function printer(): Configuration\Printer
{ {
return new Configuration\Theme; return new Configuration\Printer;
} }
/** /**

View File

@ -9,7 +9,7 @@ use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
/** /**
* @internal * @internal
*/ */
final readonly class Theme final readonly class Printer
{ {
/** /**
* Sets the theme to compact. * Sets the theme to compact.

View File

@ -89,7 +89,7 @@ final class Project
{ {
$this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s"; $this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s";
$this->assignees = "https://{$namespace}.atlassian.net/secure/ViewProfile?name=%s"; $this->assignees = "https://{$namespace}.atlassian.net/secure/ViewProfile.jspa?name=%s";
return $this; return $this;
} }

View File

@ -223,7 +223,7 @@ final class Expectation
throw new BadMethodCallException('Expectation value is not iterable.'); throw new BadMethodCallException('Expectation value is not iterable.');
} }
if (count($callbacks) == 0) { if ($callbacks === []) {
throw new InvalidArgumentException('No sequence expectations defined.'); throw new InvalidArgumentException('No sequence expectations defined.');
} }
@ -264,7 +264,7 @@ final class Expectation
$matched = false; $matched = false;
foreach ($expressions as $key => $callback) { foreach ($expressions as $key => $callback) {
if ($subject != $key) { if ($subject != $key) { // @pest-arch-ignore-line
continue; continue;
} }
@ -380,7 +380,7 @@ final class Expectation
if (self::hasExtend($name)) { if (self::hasExtend($name)) {
$extend = self::$extends[$name]->bindTo($this, Expectation::class); $extend = self::$extends[$name]->bindTo($this, Expectation::class);
if ($extend != false) { if ($extend != false) { // @pest-arch-ignore-line
return $extend; return $extend;
} }
} }
@ -509,12 +509,25 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => (bool) preg_match('/^<\?php\s+declare\(.*?strict_types\s?=\s?1.*?\);/', (string) file_get_contents($object->path)), fn (ObjectDescription $object): bool => (bool) preg_match('/^<\?php\s*(\/\*[\s\S]*?\*\/|\/\/[^\r\n]*(?:\r?\n|$)|\s)*declare\s*\(\s*strict_types\s*=\s*1\s*\)\s*;/m', (string) file_get_contents($object->path)),
'to use strict types', 'to use strict types',
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')), FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
); );
} }
/**
* Asserts that the given expectation target uses strict equality.
*/
public function toUseStrictEquality(): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), ' == ') && ! str_contains((string) file_get_contents($object->path), ' != '),
'to use strict equality',
FileLineFinder::where(fn (string $line): bool => str_contains($line, ' == ') || str_contains($line, ' != ')),
);
}
/** /**
* Asserts that the given expectation target is final. * Asserts that the given expectation target is final.
*/ */

View File

@ -152,6 +152,19 @@ final readonly class OppositeExpectation
); );
} }
/**
* Asserts that the given expectation target does not use the strict equality operator.
*/
public function toUseStrictEquality(): ArchExpectation
{
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), ' === ') && ! str_contains((string) file_get_contents($object->path), ' !== '),
'to use strict equality',
FileLineFinder::where(fn (string $line): bool => str_contains($line, ' === ') || str_contains($line, ' !== ')),
);
}
/** /**
* Asserts that the given expectation target is not final. * Asserts that the given expectation target is not final.
*/ */

View File

@ -166,7 +166,7 @@ final class TestCaseFactory
} }
PHP; PHP;
eval($classCode); // @phpstan-ignore-line eval($classCode);
} 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",

View File

@ -31,8 +31,10 @@ final class TestCaseMethodFactory
/** /**
* The test's describing, if any. * The test's describing, if any.
*
* @var array<int, string>
*/ */
public ?string $describing = null; public array $describing = [];
/** /**
* The test's description, if any. * The test's description, if any.
@ -118,9 +120,9 @@ final class TestCaseMethodFactory
} }
/** /**
* Creates the test's closure. * Sets the test's hooks, and runs any proxy to the test case.
*/ */
public function getClosure(TestCase $concrete): Closure public function setUp(TestCase $concrete): void
{ {
$concrete::flush(); // @phpstan-ignore-line $concrete::flush(); // @phpstan-ignore-line
@ -128,14 +130,29 @@ final class TestCaseMethodFactory
throw ShouldNotHappen::fromMessage('Description can not be empty.'); throw ShouldNotHappen::fromMessage('Description can not be empty.');
} }
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename); $testCase = TestSuite::getInstance()->tests->get($this->filename);
assert($testCase instanceof TestCaseFactory); assert($testCase instanceof TestCaseFactory);
$testCase->factoryProxies->proxy($concrete); $testCase->factoryProxies->proxy($concrete);
$this->factoryProxies->proxy($concrete); $this->factoryProxies->proxy($concrete);
}
/**
* Flushes the test case.
*/
public function tearDown(TestCase $concrete): void
{
$concrete::flush(); // @phpstan-ignore-line
}
/**
* Creates the test's closure.
*/
public function getClosure(): Closure
{
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename);
assert($testCase instanceof TestCaseFactory);
$method = $this; $method = $this;
return function (...$arguments) use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line return function (...$arguments) use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line
@ -186,7 +203,7 @@ final class TestCaseMethodFactory
]; ];
foreach ($this->depends as $depend) { foreach ($this->depends as $depend) {
$depend = Str::evaluable($this->describing !== null ? Str::describe($this->describing, $depend) : $depend); $depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend));
$this->attributes[] = new Attribute( $this->attributes[] = new Attribute(
\PHPUnit\Framework\Attributes\Depends::class, \PHPUnit\Framework\Attributes\Depends::class,
@ -209,10 +226,8 @@ final class TestCaseMethodFactory
$attributesCode $attributesCode
public function $methodName(...\$arguments) public function $methodName(...\$arguments)
{ {
\$test = \Pest\TestSuite::getInstance()->tests->get(self::\$__filename)->getMethod(\$this->name())->getClosure(\$this);
return \$this->__runTest( return \$this->__runTest(
\$test, \$this->__test,
...\$arguments, ...\$arguments,
); );
} }

View File

@ -43,7 +43,7 @@ if (! function_exists('beforeAll')) {
*/ */
function beforeAll(Closure $closure): void function beforeAll(Closure $closure): void
{ {
if (! is_null(DescribeCall::describing())) { if (DescribeCall::describing() !== []) {
$filename = Backtrace::file(); $filename = Backtrace::file();
throw new BeforeAllWithinDescribe($filename); throw new BeforeAllWithinDescribe($filename);
@ -205,7 +205,7 @@ if (! function_exists('afterAll')) {
*/ */
function afterAll(Closure $closure): void function afterAll(Closure $closure): void
{ {
if (! is_null(DescribeCall::describing())) { if (DescribeCall::describing() !== []) {
$filename = Backtrace::file(); $filename = Backtrace::file();
throw new AfterAllWithinDescribe($filename); throw new AfterAllWithinDescribe($filename);
@ -217,7 +217,7 @@ if (! function_exists('afterAll')) {
if (! function_exists('covers')) { if (! function_exists('covers')) {
/** /**
* Specifies which classes, or functions, a test method covers. * Specifies which classes, or functions, a test case covers.
* *
* @param array<int, string>|string $classesOrFunctions * @param array<int, string>|string $classesOrFunctions
*/ */
@ -243,3 +243,38 @@ if (! function_exists('covers')) {
} }
} }
} }
if (! function_exists('mutates')) {
/**
* Specifies which classes, enums, or traits a test case mutates.
*
* @param array<int, string>|string $targets
*/
function mutates(array|string ...$targets): void
{
$filename = Backtrace::file();
$beforeEachCall = (new BeforeEachCall(TestSuite::getInstance(), $filename));
$beforeEachCall->group('__pest_mutate_only');
/** @var MutationTestRunner $runner */
$runner = Container::getInstance()->get(MutationTestRunner::class);
/** @var \Pest\Mutate\Repositories\ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;
$paths = $configurationRepository->cliConfiguration->toArray()['paths'] ?? false;
if ($runner->isEnabled() && ! $everything && ! is_array($classes) && ! is_array($paths)) {
$beforeEachCall->only('__pest_mutate_only');
}
/** @var ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$paths = $configurationRepository->cliConfiguration->toArray()['paths'] ?? false;
if (! is_array($paths)) {
$configurationRepository->globalConfiguration('default')->class(...$targets); // @phpstan-ignore-line
}
}
}

View File

@ -40,7 +40,7 @@ final class KernelDump
*/ */
public function disable(): void public function disable(): void
{ {
@ob_clean(); // @phpstan-ignore-line @ob_clean();
if ($this->buffer !== '') { if ($this->buffer !== '') {
$this->flush(); $this->flush();

View File

@ -18,6 +18,7 @@ use PHPUnit\Event\Test\Failed;
use PHPUnit\Event\Test\MarkedIncomplete; use PHPUnit\Event\Test\MarkedIncomplete;
use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\Test\Skipped;
use PHPUnit\Event\TestSuite\TestSuite; use PHPUnit\Event\TestSuite\TestSuite;
use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider;
use PHPUnit\Framework\Exception as FrameworkException; use PHPUnit\Framework\Exception as FrameworkException;
use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult; use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult;
@ -147,6 +148,13 @@ final readonly class Converter
*/ */
public function getTestSuiteName(TestSuite $testSuite): string public function getTestSuiteName(TestSuite $testSuite): string
{ {
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
$firstTest = $this->getFirstTest($testSuite);
if ($firstTest instanceof \PHPUnit\Event\Code\TestMethod) {
return $this->getTestMethodNameWithoutDatasetSuffix($firstTest);
}
}
$name = $testSuite->name(); $name = $testSuite->name();
if (! str_starts_with($name, self::PREFIX)) { if (! str_starts_with($name, self::PREFIX)) {
@ -168,6 +176,35 @@ final readonly class Converter
* Gets the test suite location. * Gets the test suite location.
*/ */
public function getTestSuiteLocation(TestSuite $testSuite): ?string public function getTestSuiteLocation(TestSuite $testSuite): ?string
{
$firstTest = $this->getFirstTest($testSuite);
if (! $firstTest instanceof \PHPUnit\Event\Code\TestMethod) {
return null;
}
$path = $firstTest->testDox()->prettifiedClassName();
$classRelativePath = $this->toRelativePath($path);
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
$methodName = $this->getTestMethodNameWithoutDatasetSuffix($firstTest);
return "$classRelativePath::$methodName";
}
return $classRelativePath;
}
/**
* Gets the prettified test method name without dataset-related suffix.
*/
private function getTestMethodNameWithoutDatasetSuffix(TestMethod $testMethod): string
{
return Str::beforeLast($testMethod->testDox()->prettifiedMethodName(), ' with data set ');
}
/**
* Gets the first test from the test suite.
*/
private function getFirstTest(TestSuite $testSuite): ?TestMethod
{ {
$tests = $testSuite->tests()->asArray(); $tests = $testSuite->tests()->asArray();
@ -181,9 +218,7 @@ final readonly class Converter
throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); throw ShouldNotHappen::fromMessage('Not an instance of TestMethod');
} }
$path = $firstTest->testDox()->prettifiedClassName(); return $firstTest;
return $this->toRelativePath($path);
} }
/** /**

View File

@ -38,7 +38,7 @@ final class ServiceMessage
{ {
return new self('testSuiteStarted', [ return new self('testSuiteStarted', [
'name' => $name, 'name' => $name,
'locationHint' => $location === null ? null : "file://$location", 'locationHint' => $location === null ? null : "pest_qn://$location",
]); ]);
} }

View File

@ -6,6 +6,7 @@ namespace Pest\PendingCalls;
use Closure; use Closure;
use Pest\PendingCalls\Concerns\Describable; use Pest\PendingCalls\Concerns\Describable;
use Pest\Support\Arr;
use Pest\Support\Backtrace; use Pest\Support\Backtrace;
use Pest\Support\ChainableClosure; use Pest\Support\ChainableClosure;
use Pest\Support\HigherOrderMessageCollection; use Pest\Support\HigherOrderMessageCollection;
@ -54,7 +55,7 @@ final class AfterEachCall
$proxies = $this->proxies; $proxies = $this->proxies;
$afterEachTestCase = ChainableClosure::boundWhen( $afterEachTestCase = ChainableClosure::boundWhen(
fn (): bool => is_null($describing) || $this->__describing === $describing, fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
)->bindTo($this, self::class); )->bindTo($this, self::class);

View File

@ -7,6 +7,7 @@ namespace Pest\PendingCalls;
use Closure; use Closure;
use Pest\Exceptions\AfterBeforeTestFunction; use Pest\Exceptions\AfterBeforeTestFunction;
use Pest\PendingCalls\Concerns\Describable; use Pest\PendingCalls\Concerns\Describable;
use Pest\Support\Arr;
use Pest\Support\Backtrace; use Pest\Support\Backtrace;
use Pest\Support\ChainableClosure; use Pest\Support\ChainableClosure;
use Pest\Support\HigherOrderMessageCollection; use Pest\Support\HigherOrderMessageCollection;
@ -63,12 +64,12 @@ final class BeforeEachCall
$beforeEachTestCall = function (TestCall $testCall) use ($describing): void { $beforeEachTestCall = function (TestCall $testCall) use ($describing): void {
if ($this->describing !== null) { if ($this->describing !== []) {
if ($describing !== $this->describing) { if (Arr::last($describing) !== Arr::last($this->describing)) {
return; return;
} }
if ($describing !== $testCall->describing) { if (! in_array(Arr::last($describing), $testCall->describing, true)) {
return; return;
} }
} }
@ -77,7 +78,7 @@ final class BeforeEachCall
}; };
$beforeEachTestCase = ChainableClosure::boundWhen( $beforeEachTestCase = ChainableClosure::boundWhen(
fn (): bool => is_null($describing) || $this->__describing === $describing, fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
)->bindTo($this, self::class); )->bindTo($this, self::class);
@ -96,7 +97,7 @@ final class BeforeEachCall
*/ */
public function after(Closure $closure): self public function after(Closure $closure): self
{ {
if ($this->describing === null) { if ($this->describing === []) {
throw new AfterBeforeTestFunction($this->filename); throw new AfterBeforeTestFunction($this->filename);
} }

View File

@ -11,11 +11,15 @@ trait Describable
{ {
/** /**
* Note: this is property is not used; however, it gets added automatically by rector php. * Note: this is property is not used; however, it gets added automatically by rector php.
*
* @var array<int, string>
*/ */
public string $__describing; public array $__describing;
/** /**
* The describing of the test case. * The describing of the test case.
*
* @var array<int, string>
*/ */
public ?string $describing = null; public array $describing = [];
} }

View File

@ -15,8 +15,10 @@ final class DescribeCall
{ {
/** /**
* The current describe call. * The current describe call.
*
* @var array<int, string>
*/ */
private static ?string $describing = null; private static array $describing = [];
/** /**
* The describe "before each" call. * The describe "before each" call.
@ -37,8 +39,10 @@ final class DescribeCall
/** /**
* What is the current describing. * What is the current describing.
*
* @return array<int, string>
*/ */
public static function describing(): ?string public static function describing(): array
{ {
return self::$describing; return self::$describing;
} }
@ -50,12 +54,12 @@ final class DescribeCall
{ {
unset($this->currentBeforeEachCall); unset($this->currentBeforeEachCall);
self::$describing = $this->description; self::$describing[] = $this->description;
try { try {
($this->tests)(); ($this->tests)();
} finally { } finally {
self::$describing = null; array_pop(self::$describing);
} }
} }
@ -71,7 +75,7 @@ final class DescribeCall
if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) { if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) {
$this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename); $this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename);
$this->currentBeforeEachCall->describing = $this->description; $this->currentBeforeEachCall->describing[] = $this->description;
} }
$this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line $this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Pest\PendingCalls; namespace Pest\PendingCalls;
use Closure; use Closure;
use Pest\Concerns\Testable;
use Pest\Exceptions\InvalidArgumentException; use Pest\Exceptions\InvalidArgumentException;
use Pest\Exceptions\TestDescriptionMissing; use Pest\Exceptions\TestDescriptionMissing;
use Pest\Factories\Attribute; use Pest\Factories\Attribute;
@ -25,9 +26,9 @@ use PHPUnit\Framework\TestCase;
/** /**
* @internal * @internal
* *
* @mixin HigherOrderCallables|TestCase * @mixin HigherOrderCallables|TestCase|Testable
*/ */
final class TestCall final class TestCall // @phpstan-ignore-line
{ {
use Describable; use Describable;
@ -75,7 +76,7 @@ final class TestCall
throw new TestDescriptionMissing($this->filename); throw new TestDescriptionMissing($this->filename);
} }
$description = is_null($this->describing) $description = $this->describing === []
? $this->description ? $this->description
: Str::describe($this->describing, $this->description); : Str::describe($this->describing, $this->description);
@ -358,8 +359,8 @@ final class TestCall
public function todo(// @phpstan-ignore-line public function todo(// @phpstan-ignore-line
array|string|null $note = null, array|string|null $note = null,
array|string|null $assignee = null, array|string|null $assignee = null,
array|string|null $issue = null, array|string|int|null $issue = null,
array|string|null $pr = null, array|string|int|null $pr = null,
): self { ): self {
$this->skip('__TODO__'); $this->skip('__TODO__');
@ -390,8 +391,8 @@ final class TestCall
public function wip(// @phpstan-ignore-line public function wip(// @phpstan-ignore-line
array|string|null $note = null, array|string|null $note = null,
array|string|null $assignee = null, array|string|null $assignee = null,
array|string|null $issue = null, array|string|int|null $issue = null,
array|string|null $pr = null, array|string|int|null $pr = null,
): self { ): self {
if ($issue !== null) { if ($issue !== null) {
$this->issue($issue); $this->issue($issue);
@ -418,8 +419,8 @@ final class TestCall
public function done(// @phpstan-ignore-line public function done(// @phpstan-ignore-line
array|string|null $note = null, array|string|null $note = null,
array|string|null $assignee = null, array|string|null $assignee = null,
array|string|null $issue = null, array|string|int|null $issue = null,
array|string|null $pr = null, array|string|int|null $pr = null,
): self { ): self {
if ($issue !== null) { if ($issue !== null) {
$this->issue($issue); $this->issue($issue);
@ -682,7 +683,7 @@ final class TestCall
throw new TestDescriptionMissing($this->filename); throw new TestDescriptionMissing($this->filename);
} }
if (! is_null($this->describing)) { if ($this->describing !== []) {
$this->testCaseMethod->describing = $this->describing; $this->testCaseMethod->describing = $this->describing;
$this->testCaseMethod->description = Str::describe($this->describing, $this->description); $this->testCaseMethod->description = Str::describe($this->describing, $this->description);
} else { } else {

View File

@ -54,7 +54,7 @@ final class UsesCall
} }
/** /**
* @deprecated Use `pest()->theme()->compact()` instead. * @deprecated Use `pest()->printer()->compact()` instead.
*/ */
public function compact(): self public function compact(): self
{ {

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string function version(): string
{ {
return '3.0.0'; return '3.5.0';
} }
function testDirectory(string $file = ''): string function testDirectory(string $file = ''): string

View File

@ -122,6 +122,9 @@ final class WrapperRunner implements RunnerInterface
$parameters = array_merge($parameters, $options->passthruPhp); $parameters = array_merge($parameters, $options->passthruPhp);
} }
/** @var array<int, non-empty-string> $parameters */
$parameters = $this->handleLaravelHerd($parameters);
$parameters[] = $wrapper; $parameters[] = $wrapper;
$this->parameters = $parameters; $this->parameters = $parameters;
@ -153,6 +156,21 @@ final class WrapperRunner implements RunnerInterface
return $this->complete($result); return $this->complete($result);
} }
/**
* Handles Laravel Herd's debug and coverage modes.
*
* @param array<string> $parameters
* @return array<string>
*/
private function handleLaravelHerd(array $parameters): array
{
if (isset($_ENV['HERD_DEBUG_INI'])) {
return array_merge($parameters, ['-c', $_ENV['HERD_DEBUG_INI']]);
}
return $parameters;
}
private function startWorkers(): void private function startWorkers(): void
{ {
for ($token = 1; $token <= $this->options->processes; $token++) { for ($token = 1; $token <= $this->options->processes; $token++) {

View File

@ -81,4 +81,14 @@ final class Arr
return $results; return $results;
} }
/**
* Returns the value of the last element or false for empty array
*
* @param array<array-key, mixed> $array
*/
public static function last(array $array): mixed
{
return end($array);
}
} }

View File

@ -20,13 +20,13 @@ final class Closure
*/ */
public static function bind(?BaseClosure $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure public static function bind(?BaseClosure $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure
{ {
if ($closure == null) { if (! $closure instanceof \Closure) {
throw ShouldNotHappen::fromMessage('Could not bind null closure.'); throw ShouldNotHappen::fromMessage('Could not bind null closure.');
} }
$closure = BaseClosure::bind($closure, $newThis, $newScope); $closure = BaseClosure::bind($closure, $newThis, $newScope);
if ($closure == false) { if (! $closure instanceof \Closure) {
throw ShouldNotHappen::fromMessage('Could not bind closure.'); throw ShouldNotHappen::fromMessage('Could not bind closure.');
} }

View File

@ -103,10 +103,14 @@ final class Str
/** /**
* Creates a describe block as `$describeDescription` → `$testDescription` format. * Creates a describe block as `$describeDescription` → `$testDescription` format.
*
* @param array<int, string> $describeDescriptions
*/ */
public static function describe(string $describeDescription, string $testDescription): string public static function describe(array $describeDescriptions, string $testDescription): string
{ {
return sprintf('`%s` → %s', $describeDescription, $testDescription); $descriptionComponents = [...$describeDescriptions, $testDescription];
return sprintf(str_repeat('`%s` → ', count($describeDescriptions)).'%s', ...$descriptionComponents);
} }
/** /**

View File

@ -1,5 +1,5 @@
Pest Testing Framework 3.0.0. Pest Testing Framework 3.5.0.
USAGE: pest <file> [options] USAGE: pest <file> [options]
@ -35,6 +35,7 @@
--exclude-group [name] ........... Exclude tests from the specified group(s) --exclude-group [name] ........... Exclude tests from the specified group(s)
--covers [name] ................. Only run tests that intend to cover [name] --covers [name] ................. Only run tests that intend to cover [name]
--uses [name] ..................... Only run tests that intend to use [name] --uses [name] ..................... Only run tests that intend to use [name]
--requires-php-extension [name] Only run tests that require PHP extension [name]
--list-test-files ................................ List available test files --list-test-files ................................ List available test files
--list-tests .......................................... List available tests --list-tests .......................................... List available tests
--list-tests-xml [file] ................. List available tests in XML format --list-tests-xml [file] ................. List available tests in XML format

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 7 todos TODO Tests\Features\Todo - 28 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -24,6 +24,52 @@
↓ it may have an associated PR #1 ↓ it may have an associated PR #1
↓ it may have an associated note ↓ it may have an associated note
// a note // a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note
PASS Tests\CustomTestCase\ChildTest PASS Tests\CustomTestCase\ChildTest
✓ override method ✓ override method
@ -34,6 +80,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 17 todos, 3 passed (3 assertions) Tests: 38 todos, 3 passed (20 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 7 todos TODO Tests\Features\Todo - 28 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -24,6 +24,52 @@
↓ it may have an associated PR #1 ↓ it may have an associated PR #1
↓ it may have an associated note ↓ it may have an associated note
// a note // a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note
PASS Tests\CustomTestCase\ChildTest PASS Tests\CustomTestCase\ChildTest
✓ override method ✓ override method
@ -34,6 +80,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 17 todos, 3 passed (3 assertions) Tests: 38 todos, 3 passed (20 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 7 todos TODO Tests\Features\Todo - 28 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -24,6 +24,52 @@
↓ it may have an associated PR #1 ↓ it may have an associated PR #1
↓ it may have an associated note ↓ it may have an associated note
// a note // a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note
PASS Tests\CustomTestCase\ChildTest PASS Tests\CustomTestCase\ChildTest
✓ override method ✓ override method
@ -34,6 +80,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 17 todos, 3 passed (3 assertions) Tests: 38 todos, 3 passed (20 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 7 todos TODO Tests\Features\Todo - 28 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -24,6 +24,52 @@
↓ it may have an associated PR #1 ↓ it may have an associated PR #1
↓ it may have an associated note ↓ it may have an associated note
// a note // a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note
PASS Tests\CustomTestCase\ChildTest PASS Tests\CustomTestCase\ChildTest
✓ override method ✓ override method
@ -34,6 +80,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 17 todos, 3 passed (3 assertions) Tests: 38 todos, 3 passed (20 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -1,3 +1,3 @@
Pest Testing Framework 3.0.0. Pest Testing Framework 3.5.0.

View File

@ -1,4 +1,4 @@
##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='file://tests/.tests/Failure.php' flowId='1234'] ##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='pest_qn://tests/.tests/Failure.php' flowId='1234']
##teamcity[testCount count='8' flowId='1234'] ##teamcity[testCount count='8' flowId='1234']
##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234'] ##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234']
##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at tests/.tests/Failure.php:6' type='comparisonFailure' actual='true' expected='false' flowId='1234'] ##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at tests/.tests/Failure.php:6' type='comparisonFailure' actual='true' expected='false' flowId='1234']

View File

@ -1,11 +1,15 @@
##teamcity[testSuiteStarted name='Tests/tests/SuccessOnly' locationHint='file://tests/.tests/SuccessOnly.php' flowId='1234'] ##teamcity[testSuiteStarted name='Tests/tests/SuccessOnly' locationHint='pest_qn://tests/.tests/SuccessOnly.php' flowId='1234']
##teamcity[testCount count='2' flowId='1234'] ##teamcity[testCount count='3' flowId='1234']
##teamcity[testStarted name='it can pass with comparison' locationHint='pest_qn://tests/.tests/SuccessOnly.php::it can pass with comparison' flowId='1234'] ##teamcity[testStarted name='it can pass with comparison' locationHint='pest_qn://tests/.tests/SuccessOnly.php::it can pass with comparison' flowId='1234']
##teamcity[testFinished name='it can pass with comparison' duration='100000' flowId='1234'] ##teamcity[testFinished name='it can pass with comparison' duration='100000' flowId='1234']
##teamcity[testStarted name='can also pass' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can also pass' flowId='1234'] ##teamcity[testStarted name='can also pass' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can also pass' flowId='1234']
##teamcity[testFinished name='can also pass' duration='100000' flowId='1234'] ##teamcity[testFinished name='can also pass' duration='100000' flowId='1234']
##teamcity[testSuiteStarted name='can pass with dataset' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset' flowId='1234']
##teamcity[testStarted name='can pass with dataset with data set "(true)"' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset with data set "(true)"' flowId='1234']
##teamcity[testFinished name='can pass with dataset with data set "(true)"' duration='100000' flowId='1234']
##teamcity[testSuiteFinished name='can pass with dataset' flowId='1234']
##teamcity[testSuiteFinished name='Tests/tests/SuccessOnly' flowId='1234'] ##teamcity[testSuiteFinished name='Tests/tests/SuccessOnly' flowId='1234']
Tests: 2 passed (2 assertions) Tests: 3 passed (3 assertions)
Duration: 1.00s Duration: 1.00s

View File

@ -27,6 +27,8 @@
PASS Tests\Features\AfterEach PASS Tests\Features\AfterEach
✓ it does not get executed before the test ✓ it does not get executed before the test
✓ it gets executed after the test ✓ it gets executed after the test
✓ outer → inner → it does not get executed before the test
✓ outer → inner → it should call all parent afterEach functions
PASS Tests\Features\Assignee PASS Tests\Features\Assignee
✓ it may be associated with an assignee [@nunomaduro, @taylorotwell] ✓ it may be associated with an assignee [@nunomaduro, @taylorotwell]
@ -40,6 +42,9 @@
PASS Tests\Features\BeforeEach PASS Tests\Features\BeforeEach
✓ it gets executed before each test ✓ it gets executed before each test
✓ it gets executed before each test once again ✓ it gets executed before each test once again
✓ outer → inner → it should call all parent beforeEach functions
✓ with expectations → nested block → test
✓ with expectations → test
PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations
✓ runs 1 ✓ runs 1
@ -178,6 +183,14 @@
✓ it may be used with high order with dataset "informal" ✓ it may be used with high order with dataset "informal"
✓ it may be used with high order even when bound with dataset "formal" ✓ it may be used with high order even when bound with dataset "formal"
✓ it may be used with high order even when bound with dataset "informal" ✓ it may be used with high order even when bound with dataset "informal"
✓ with on nested describe → nested → before inner describe block with (1)
✓ with on nested describe → nested → describe → it should include the with value from all parent describe blocks with (1) / (2)
✓ with on nested describe → nested → describe → should include the with value from all parent describe blocks and the test with (1) / (2) / (3)
✓ with on nested describe → nested → after inner describe block with (1)
✓ after describe block with (5)
✓ it may be used with high order after describe block with dataset "formal"
✓ it may be used with high order after describe block with dataset "informal"
✓ after describe block with named dataset with ('after')
PASS Tests\Features\Depends PASS Tests\Features\Depends
✓ first ✓ first
@ -188,6 +201,13 @@
✓ depends run test only once ✓ depends run test only once
✓ it asserts true is true ✓ it asserts true is true
✓ depends works with the correct test name ✓ depends works with the correct test name
✓ describe block → first in describe
✓ describe block → second in describe
✓ describe block → third in describe
✓ describe block → nested describe → first in nested describe
✓ describe block → nested describe → second in nested describe
✓ describe block → nested describe → third in nested describe
✓ depends on test after describe block
PASS Tests\Features\DependsInheritance PASS Tests\Features\DependsInheritance
✓ it is a test ✓ it is a test
@ -215,6 +235,7 @@
✓ depends on describe → bar ✓ depends on describe → bar
✓ depends on describe using with → foo with (3) ✓ depends on describe using with → foo with (3)
✓ depends on describe using with → bar with (3) ✓ depends on describe using with → bar with (3)
✓ with test after describe → it should run the before each
PASS Tests\Features\DescriptionLess PASS Tests\Features\DescriptionLess
✓ get 'foo' ✓ get 'foo'
@ -968,6 +989,16 @@
✓ it can handle a non-defined exception ✓ it can handle a non-defined exception
✓ it can handle a class not found Error ✓ it can handle a class not found Error
PASS Tests\Features\Expect\toUseStrictEquality
✓ missing strict equality
✓ has strict equality
✓ opposite missing strict equality
✓ opposite has strict equality
PASS Tests\Features\Expect\toUseStrictTypes
✓ pass
✓ failures
PASS Tests\Features\Expect\toUseTrait PASS Tests\Features\Expect\toUseTrait
✓ pass ✓ pass
✓ failures ✓ failures
@ -1057,9 +1088,22 @@
✓ nested → it may have static note and runtime note ✓ nested → it may have static note and runtime note
// This is before each static note // This is before each static note
// This is describe static note // This is describe static note
// This is before each describe static note
// This is a static note within describe // This is a static note within describe
// This is before each runtime note // This is before each runtime note
// This is before each describe runtime note
// This is a runtime note within describe // This is a runtime note within describe
✓ nested → describe nested within describe → it may have a static note and runtime note
// This is before each static note
// This is describe static note
// This is before each describe static note
// This is a nested describe static note
// This is before each nested describe static note
// This is a static note within a nested describe
// This is before each runtime note
// This is before each describe runtime note
// This is before each nested describe runtime note
// This is a runtime note within a nested describe
✓ multiple notes ✓ multiple notes
// This is before each static note // This is before each static note
// This is before each runtime note // This is before each runtime note
@ -1189,6 +1233,23 @@
✓ multiple times with repeat iterator with multiple dataset ('c') / ('d') @ repetition 2 of 2 ✓ multiple times with repeat iterator with multiple dataset ('c') / ('d') @ repetition 2 of 2
✓ multiple times with repeat iterator with multiple dataset ('c') / ('e') @ repetition 2 of 2 ✓ multiple times with repeat iterator with multiple dataset ('c') / ('e') @ repetition 2 of 2
✓ multiple times with repeat iterator with multiple dataset ('c') / ('f') @ repetition 2 of 2 ✓ multiple times with repeat iterator with multiple dataset ('c') / ('f') @ repetition 2 of 2
✓ describe blocks → multiple times @ repetition 1 of 3
✓ describe blocks → multiple times @ repetition 2 of 3
✓ describe blocks → multiple times @ repetition 3 of 3
✓ describe blocks → describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 1 of 3
✓ describe blocks → describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 2 of 3
✓ describe blocks → describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 3 of 3
✓ describe blocks → describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 1 of 2
✓ describe blocks → describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 2 of 2
✓ describe blocks → describe with repeat → nested describe without repeat → test with no repeat should repeat the number of times specified in the parent's parent describe block @ repetition 1 of 3
✓ describe blocks → describe with repeat → nested describe without repeat → test with no repeat should repeat the number of times specified in the parent's parent describe block @ repetition 2 of 3
✓ describe blocks → describe with repeat → nested describe without repeat → test with no repeat should repeat the number of times specified in the parent's parent describe block @ repetition 3 of 3
✓ describe blocks → describe with repeat → nested describe without repeat → test with repeat should repeat the number of times specified in the test @ repetition 1 of 2
✓ describe blocks → describe with repeat → nested describe without repeat → test with repeat should repeat the number of times specified in the test @ repetition 2 of 2
✓ describe blocks → describe with repeat → nested describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 1 of 2
✓ describe blocks → describe with repeat → nested describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 2 of 2
✓ describe blocks → describe with repeat → nested describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 1 of 2
✓ describe blocks → describe with repeat → nested describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 2 of 2
PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory1\TestFileInNestedDirectoryWithDatasetsFile PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory1\TestFileInNestedDirectoryWithDatasetsFile
✓ uses dataset with (1) ✓ uses dataset with (1)
@ -1245,6 +1306,14 @@
- it skips when skip after assertion - it skips when skip after assertion
- it can use something in the test case as a condition → This test was skipped - it can use something in the test case as a condition → This test was skipped
- it can user higher order callables and skip - it can user higher order callables and skip
- skip on describe → skipped tests → nested inside skipped block → it should not execute
- skip on describe → skipped tests → it should not execute
✓ skip on describe → it should execute
- skip on beforeEach → skipped tests → nested inside skipped block → it should not execute
- skip on beforeEach → skipped tests → it should not execute
✓ skip on beforeEach → it should execute
✓ it does not skip after the describe block
- it can skip after the describe block
WARN Tests\Features\SkipOnPhp WARN Tests\Features\SkipOnPhp
✓ it can run on php version ✓ it can run on php version
@ -1265,7 +1334,7 @@
✓ nested → it may be associated with an ticket #1, #4, #5, #6, #3 ✓ nested → it may be associated with an ticket #1, #4, #5, #6, #3
// an note between an the ticket // an note between an the ticket
PASS Tests\Features\Todo - 7 todos PASS Tests\Features\Todo - 28 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -1275,6 +1344,54 @@
↓ it may have an associated PR #1 ↓ it may have an associated PR #1
↓ it may have an associated note ↓ it may have an associated note
// a note // a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
✓ todo on describe → it should execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
✓ todo on beforeEach → it should execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note
WARN Tests\Features\Warnings WARN Tests\Features\Warnings
! warning → Undefined property: P\Tests\Features\Warnings::$fooqwdfwqdfqw ! warning → Undefined property: P\Tests\Features\Warnings::$fooqwdfwqdfqw
@ -1305,6 +1422,7 @@
✓ it executes tests in the Helpers directory ✓ it executes tests in the Helpers directory
PASS Tests\Hooks\AfterEachTest PASS Tests\Hooks\AfterEachTest
✓ nested → nested afterEach execution order
✓ global afterEach execution order ✓ global afterEach execution order
PASS Tests\Hooks\BeforeEachTest PASS Tests\Hooks\BeforeEachTest
@ -1381,7 +1499,7 @@
✓ it proxies to uses call ✓ it proxies to uses call
PASS Tests\Unit\Configuration\Theme PASS Tests\Unit\Configuration\Theme
✓ it creates a theme instance ✓ it creates a printer instance
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
@ -1422,6 +1540,13 @@
✓ preset invalid name ✓ preset invalid name
✓ preset → myFramework ✓ preset → myFramework
PASS Tests\Unit\Support\Arr
✓ last → it should return false for an empty arary
✓ last → it should return the last element for an array with a single element
✓ last → it should return the last element for an array without changing the internal pointer
✓ last → it should return the last element for an associative array without changing the internal pointer
✓ last → it should return the last element for an mixed key array without changing the internal pointer
PASS Tests\Unit\Support\Backtrace PASS Tests\Unit\Support\Backtrace
✓ it gets file name from called file ✓ it gets file name from called file
@ -1573,4 +1698,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1088 passed (2615 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1144 passed (2736 assertions)

View File

@ -9,3 +9,7 @@ it('can pass with comparison', function () {
test('can also pass', function () { test('can also pass', function () {
expect("string")->toBeString(); expect("string")->toBeString();
}); });
test('can pass with dataset', function ($value) {
expect($value)->toEqual(true);
})->with([true]);

View File

@ -26,3 +26,25 @@ it('gets executed after the test', function () {
afterEach(function () { afterEach(function () {
$this->state->bar = 2; $this->state->bar = 2;
}); });
describe('outer', function () {
afterEach(function () {
$this->state->bar++;
});
describe('inner', function () {
afterEach(function () {
$this->state->bar++;
});
it('does not get executed before the test', function () {
expect($this->state)->toHaveProperty('bar');
expect($this->state->bar)->toBe(2);
});
it('should call all parent afterEach functions', function () {
expect($this->state)->toHaveProperty('bar');
expect($this->state->bar)->toBe(4);
});
});
});

View File

@ -25,3 +25,29 @@ it('gets executed before each test once again', function () {
beforeEach(function () { beforeEach(function () {
$this->bar++; $this->bar++;
}); });
describe('outer', function () {
beforeEach(function () {
$this->bar++;
});
describe('inner', function () {
beforeEach(function () {
$this->bar++;
});
it('should call all parent beforeEach functions', function () {
expect($this->bar)->toBe(3);
});
});
});
describe('with expectations', function () {
beforeEach()->expect(true)->toBeTrue();
describe('nested block', function () {
test('test', function () {});
});
test('test', function () {});
});

View File

@ -392,3 +392,40 @@ it('may be used with high order even when bound')
->with('greeting-bound') ->with('greeting-bound')
->expect(fn (string $greeting) => $greeting) ->expect(fn (string $greeting) => $greeting)
->throws(InvalidArgumentException::class); ->throws(InvalidArgumentException::class);
describe('with on nested describe', function () {
describe('nested', function () {
test('before inner describe block', function (...$args) {
expect($args)->toBe([1]);
});
describe('describe', function () {
it('should include the with value from all parent describe blocks', function (...$args) {
expect($args)->toBe([1, 2]);
});
test('should include the with value from all parent describe blocks and the test', function (...$args) {
expect($args)->toBe([1, 2, 3]);
})->with([3]);
})->with([2]);
test('after inner describe block', function (...$args) {
expect($args)->toBe([1]);
});
})->with([1]);
});
test('after describe block', function (...$args) {
expect($args)->toBe([5]);
})->with([5]);
it('may be used with high order after describe block')
->with('greeting-string')
->expect(fn (string $greeting) => $greeting)
->throwsNoExceptions();
dataset('after-describe', ['after']);
test('after describe block with named dataset', function (...$args) {
expect($args)->toBe(['after']);
})->with('after-describe');

View File

@ -36,3 +36,43 @@ test('depends run test only once', function () use (&$runCounter) {
// Regression tests. See https://github.com/pestphp/pest/pull/216 // Regression tests. See https://github.com/pestphp/pest/pull/216
it('asserts true is true')->assertTrue(true); it('asserts true is true')->assertTrue(true);
test('depends works with the correct test name')->assertTrue(true)->depends('it asserts true is true'); test('depends works with the correct test name')->assertTrue(true)->depends('it asserts true is true');
describe('describe block', function () {
$runCounter = 0;
test('first in describe', function () use (&$runCounter) {
$runCounter++;
expect(true)->toBeTrue();
});
test('second in describe', function () use (&$runCounter) {
expect($runCounter)->toBe(1);
$runCounter++;
})->depends('first in describe');
test('third in describe', function () use (&$runCounter) {
expect($runCounter)->toBe(2);
})->depends('second in describe');
describe('nested describe', function () {
$runCounter = 0;
test('first in nested describe', function () use (&$runCounter) {
$runCounter++;
expect(true)->toBeTrue();
});
test('second in nested describe', function () use (&$runCounter) {
expect($runCounter)->toBe(1);
$runCounter++;
})->depends('first in nested describe');
test('third in nested describe', function () use (&$runCounter) {
expect($runCounter)->toBe(2);
})->depends('second in nested describe');
});
});
test('depends on test after describe block', function () use (&$runCounter) {
expect($runCounter)->toBe(2);
})->depends('first', 'second');

View File

@ -96,3 +96,15 @@ describe('depends on describe using with', function () {
expect($foo + $foo)->toBe(6); expect($foo + $foo)->toBe(6);
})->depends('foo'); })->depends('foo');
})->with([3]); })->with([3]);
describe('with test after describe', function () {
beforeEach(function () {
$this->count++;
});
describe('foo', function () {});
it('should run the before each', function () {
expect($this->count)->toBe(2);
});
});

View File

@ -0,0 +1,21 @@
<?php
use Pest\Arch\Exceptions\ArchExpectationFailedException;
test('missing strict equality')
->throws(ArchExpectationFailedException::class)
->expect('Tests\\Fixtures\\Arch\\ToUseStrictEquality\\NotStrictEquality')
->toUseStrictEquality();
test('has strict equality')
->expect('Tests\\Fixtures\\Arch\\ToUseStrictEquality\\StrictEquality')
->toUseStrictEquality();
test('opposite missing strict equality')
->throws(ArchExpectationFailedException::class)
->expect('Tests\\Fixtures\\Arch\\ToUseStrictEquality\\StrictEquality')
->not->toUseStrictEquality();
test('opposite has strict equality')
->expect('Tests\\Fixtures\\Arch\\ToUseStrictEquality\\NotStrictEquality')
->not->toUseStrictEquality();

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
use Pest\Arch\Exceptions\ArchExpectationFailedException;
use Tests\Fixtures\Arch\ToUseStrictTypes\HasNoStrictType;
use Tests\Fixtures\Arch\ToUseStrictTypes\HasStrictType;
use Tests\Fixtures\Arch\ToUseStrictTypes\HasStrictTypeWithCommentsAbove;
test('pass', function () {
expect(HasStrictType::class)->toUseStrictTypes()
->and(HasStrictTypeWithCommentsAbove::class)->toUseStrictTypes();
});
test('failures', function () {
expect(HasNoStrictType::class)->toUseStrictTypes();
})->throws(ArchExpectationFailedException::class);

View File

@ -21,11 +21,27 @@ it('may have static note and runtime note', function () {
})->note('This is a static note'); })->note('This is a static note');
describe('nested', function () { describe('nested', function () {
beforeEach(function () {
$this->note('This is before each describe runtime note');
})->note('This is before each describe static note');
it('may have static note and runtime note', function () { it('may have static note and runtime note', function () {
expect(true)->toBeTrue(true); expect(true)->toBeTrue(true);
$this->note('This is a runtime note within describe'); $this->note('This is a runtime note within describe');
})->note('This is a static note within describe'); })->note('This is a static note within describe');
describe('describe nested within describe', function () {
beforeEach(function () {
$this->note('This is before each nested describe runtime note');
})->note('This is before each nested describe static note');
it('may have a static note and runtime note', function () {
expect(true)->toBeTrue(true);
$this->note('This is a runtime note within a nested describe');
})->note('This is a static note within a nested describe');
})->note('This is a nested describe static note');
})->note('This is describe static note'); })->note('This is describe static note');
test('multiple notes', function () { test('multiple notes', function () {

View File

@ -43,3 +43,39 @@ test('multiple times with repeat iterator with multiple dataset', function (stri
->toBeNumeric() ->toBeNumeric()
->toBeGreaterThan(0); ->toBeGreaterThan(0);
})->repeat(times: 2)->with(['a', 'b', 'c'], ['d', 'e', 'f']); })->repeat(times: 2)->with(['a', 'b', 'c'], ['d', 'e', 'f']);
describe('describe blocks', function () {
test('multiple times', function () {
expect(true)->toBeTrue();
})->repeat(times: 3);
describe('describe with repeat', function () {
test('test with no repeat should repeat the number of times specified in the parent describe block', function () {
expect(true)->toBeTrue();
});
test('test with repeat should repeat the number of times specified in the test', function () {
expect(true)->toBeTrue();
})->repeat(times: 2);
describe('nested describe without repeat', function () {
test("test with no repeat should repeat the number of times specified in the parent's parent describe block", function () {
expect(true)->toBeTrue();
});
test('test with repeat should repeat the number of times specified in the test', function () {
expect(true)->toBeTrue();
})->repeat(times: 2);
});
describe('nested describe with repeat', function () {
test('test with no repeat should repeat the number of times specified in the parent describe block', function () {
expect(true)->toBeTrue();
});
test('test with repeat should repeat the number of times specified in the test', function () {
expect(true)->toBeTrue();
})->repeat(times: 2);
})->repeat(times: 2);
})->repeat(times: 3);
});

View File

@ -54,3 +54,81 @@ it('can user higher order callables and skip')
return $this->shouldSkip; return $this->shouldSkip;
}) })
->toBeFalse(); ->toBeFalse();
describe('skip on describe', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__skip_on_describe__→__skipped_tests__→__nested_inside_skipped_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__skip_on_describe__→__skipped_tests__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__skip_on_describe__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('skipped tests', function () {
describe('nested inside skipped block', function () {
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
});
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
})->skip();
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
describe('skip on beforeEach', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__skip_on_beforeEach__→__skipped_tests__→__nested_inside_skipped_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__skip_on_beforeEach__→__skipped_tests__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__skip_on_beforeEach__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('skipped tests', function () {
beforeEach()->skip();
describe('nested inside skipped block', function () {
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
});
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
});
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
it('does not skip after the describe block', function () {
expect(true)->toBeTrue();
});
it('can skip after the describe block', function () {
expect(true)->toBeTrue();
})->skip();

View File

@ -27,3 +27,175 @@ it('may have an associated PR', function () {
it('may have an associated note', function () { it('may have an associated note', function () {
expect(true)->toBeTrue(); expect(true)->toBeTrue();
})->todo(note: 'a note'); })->todo(note: 'a note');
describe('todo on describe', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__todo_on_describe__→__todo_block__→__nested_inside_todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__nested_inside_todo_block__→_it_should_set_the_note' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_without_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_with_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_as_well_as_the_note_from_the_test' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_without_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_with_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_as_well_as_the_note_from_the_test' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→__todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('todo block', function () {
describe('nested inside todo block', function () {
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
it('should set the note', function () {
$this->ran = true;
$this->fail();
})->todo(note: 'hi');
});
describe('describe with note', function () {
it('should apply the note to a test without a todo', function () {
$this->ran = true;
$this->fail();
});
it('should apply the note to a test with a todo', function () {
$this->ran = true;
$this->fail();
})->todo();
it('should apply the note as well as the note from the test', function () {
$this->ran = true;
$this->fail();
})->todo(note: 'test note');
describe('nested describe with note', function () {
it('should apply all parent notes to a test without a todo', function () {
$this->ran = true;
$this->fail();
});
it('should apply all parent notes to a test with a todo', function () {
$this->ran = true;
$this->fail();
})->todo();
it('should apply all parent notes as well as the note from the test', function () {
$this->ran = true;
$this->fail();
})->todo(note: 'test note');
})->todo(note: 'nested describe note');
})->todo(note: 'describe note');
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
})->todo();
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
test('todo on test after describe block', function () {
$this->fail();
})->todo();
test('todo with note on test after describe block', function () {
$this->fail();
})->todo(note: 'test note');
describe('todo on beforeEach', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__nested_inside_todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_without_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_with_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_as_well_as_the_note_from_the_test' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_without_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_with_a_todo' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_as_well_as_the_note_from_the_test' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_beforeEach__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('todo block', function () {
beforeEach()->todo();
describe('nested inside todo block', function () {
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
});
describe('describe with note', function () {
it('should apply the note to a test without a todo', function () {
$this->ran = true;
$this->fail();
});
it('should apply the note to a test with a todo', function () {
$this->ran = true;
$this->fail();
})->todo();
it('should apply the note as well as the note from the test', function () {
$this->ran = true;
$this->fail();
})->todo(note: 'test note');
describe('nested describe with note', function () {
it('should apply all parent notes to a test without a todo', function () {
$this->ran = true;
$this->fail();
});
it('should apply all parent notes to a test with a todo', function () {
$this->ran = true;
$this->fail();
})->todo();
it('should apply all parent notes as well as the note from the test', function () {
$this->ran = true;
$this->fail();
})->todo(note: 'test note');
})->todo(note: 'nested describe note');
})->todo(note: 'describe note');
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
});
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
test('todo on test after describe block with beforeEach', function () {
$this->fail();
})->todo();
test('todo with note on test after describe block with beforeEach', function () {
$this->fail();
})->todo(note: 'test note');

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Tests\Fixtures\Arch\ToUseStrictEquality;
class NotStrictEquality
{
public function test(): void
{
$a = 1;
$b = '1';
if ($a == $b) {
echo 'Equal';
}
if ($a != $b) {
echo 'Equal';
}
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Tests\Fixtures\Arch\ToUseStrictEquality;
class StrictEquality
{
public function test(): void
{
$a = 1;
$b = '1';
if ($a === $b) {
echo 'Equal';
}
if ($a !== $b) {
echo 'Equal';
}
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace Tests\Fixtures\Arch\ToUseStrictTypes;
class HasNoStrictType {}

View File

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace Tests\Fixtures\Arch\ToUseStrictTypes;
class HasStrictType {}

View File

@ -0,0 +1,11 @@
<?php
/** @noinspection PhpUnused */
// some other comment
declare(strict_types=1);
namespace Tests\Fixtures\Arch\ToUseStrictTypes;
class HasStrictTypeWithCommentsAbove {}

View File

@ -1,23 +1,79 @@
<?php <?php
beforeEach(function () {
$this->ith = 0;
});
pest()->afterEach(function () { pest()->afterEach(function () {
expect($this) expect($this)
->toHaveProperty('ith') ->toHaveProperty('ith')
->and($this->ith) ->and($this->ith)
->toBe(1); ->toBe(3);
$this->ith = 2; $this->ith++;
});
pest()->afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(4);
$this->ith++;
}); });
afterEach(function () { afterEach(function () {
expect($this) expect($this)
->toHaveProperty('ith') ->toHaveProperty('ith')
->and($this->ith) ->and($this->ith)
->toBe(2); ->toBe(5);
$this->ith++;
});
describe('nested', function () {
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(6);
$this->ith++;
});
test('nested afterEach execution order', function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(0);
$this->ith++;
});
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(7);
$this->ith++;
});
});
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBeBetween(6, 8);
$this->ith++;
}); });
test('global afterEach execution order', function () { test('global afterEach execution order', function () {
expect($this) expect($this)
->not() ->toHaveProperty('ith')
->toHaveProperty('ith'); ->and($this->ith)
->toBe(0);
$this->ith++;
}); });

View File

@ -32,7 +32,12 @@ pest()
$_SERVER['globalHook']->calls->beforeAll++; $_SERVER['globalHook']->calls->beforeAll++;
}) })
->afterEach(function () { ->afterEach(function () {
$this->ith = 0; if (! isset($this->ith)) {
return;
}
assert($this->ith === 1, 'Expected $this->ith to be 1, but got '.$this->ith);
$this->ith++;
}) })
->afterAll(function () { ->afterAll(function () {
$_SERVER['globalHook']->afterAll = 0; $_SERVER['globalHook']->afterAll = 0;
@ -57,12 +62,12 @@ pest()->in('Hooks')
$_SERVER['globalHook']->beforeAll = 1; $_SERVER['globalHook']->beforeAll = 1;
}) })
->afterEach(function () { ->afterEach(function () {
expect($this) if (! isset($this->ith)) {
->toHaveProperty('ith') return;
->and($this->ith) }
->toBe(0);
$this->ith = 1; assert($this->ith === 2, 'Expected $this->ith to be 1, but got '.$this->ith);
$this->ith++;
}) })
->afterAll(function () { ->afterAll(function () {
expect($_SERVER['globalHook']) expect($_SERVER['globalHook'])

View File

@ -1,7 +1,7 @@
<?php <?php
it('creates a theme instance', function () { it('creates a printer instance', function () {
$theme = pest()->theme(); $theme = pest()->printer();
expect($theme)->toBeInstanceOf(Pest\Configuration\Theme::class); expect($theme)->toBeInstanceOf(Pest\Configuration\Printer::class);
}); });

View File

@ -0,0 +1,49 @@
<?php
use Pest\Support\Arr;
describe('last', function () {
it('should return false for an empty arary', function () {
expect(Arr::last([]))->toBeFalse();
});
it('should return the last element for an array with a single element', function () {
expect(Arr::last([1]))->toBe(1);
});
it('should return the last element for an array without changing the internal pointer', function () {
$array = [1, 2, 3];
expect(Arr::last($array))->toBe(3);
expect(current($array))->toBe(1);
next($array);
expect(current($array))->toBe(2);
expect(Arr::last($array))->toBe(3);
expect(current($array))->toBe(2);
});
it('should return the last element for an associative array without changing the internal pointer', function () {
$array = ['first' => 1, 'second' => 2, 'third' => 3];
expect(Arr::last($array))->toBe(3);
expect(current($array))->toBe(1);
next($array);
expect(current($array))->toBe(2);
expect(Arr::last($array))->toBe(3);
expect(current($array))->toBe(2);
});
it('should return the last element for an mixed key array without changing the internal pointer', function () {
$array = ['first' => 1, 2, 'third' => 3];
expect(Arr::last($array))->toBe(3);
expect(current($array))->toBe(1);
next($array);
expect(current($array))->toBe(2);
expect(Arr::last($array))->toBe(3);
expect(current($array))->toBe(2);
});
});

View File

@ -36,8 +36,8 @@ test('junit output', function () use ($normalizedPath, $run) {
expect($result['testsuite']['@attributes']) expect($result['testsuite']['@attributes'])
->name->toBe('Tests\tests\SuccessOnly') ->name->toBe('Tests\tests\SuccessOnly')
->file->toBe($normalizedPath('tests/.tests/SuccessOnly.php')) ->file->toBe($normalizedPath('tests/.tests/SuccessOnly.php'))
->tests->toBe('2') ->tests->toBe('3')
->assertions->toBe('2') ->assertions->toBe('3')
->errors->toBe('0') ->errors->toBe('0')
->failures->toBe('0') ->failures->toBe('0')
->skipped->toBe('0'); ->skipped->toBe('0');

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1078 passed (2591 assertions)') ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1134 passed (2712 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();