Compare commits

...

99 Commits

Author SHA1 Message Date
fb0eef4200 release: 1.2.0 2021-05-13 00:37:55 +01:00
819da37b89 Merge pull request #292 from shuvroroy/patch-1
Remove unused import
2021-05-12 21:21:59 +01:00
8eb9c408a9 Merge pull request #296 from pestphp/feature/php-cs-fixer-3
chore: migrate to PHP-CS-Fixer 3.x
2021-05-12 15:14:06 +01:00
1f10b46402 chore: migrate to PHP-CS-Fixer 3.x 2021-05-12 11:26:09 +01:00
7bb12b73e8 Merge pull request #288 from gregorip02/master
Ignore the absence of the tests folder
2021-05-11 08:19:14 +01:00
b8103697c9 Remove unused import 2021-05-07 12:45:55 +06:00
ca3f8b5702 Merge pull request #291 from olivernybroe/feat-junit
Add Junit support
2021-05-05 17:13:58 +01:00
daa01ea44b Merge pull request #283 from faustbrian/test-directory-config
make test directory configurable
2021-05-05 11:07:10 +01:00
26d577f9c5 separate directory and path by / 2021-05-05 05:07:17 +03:00
43fb711251 Merge branch 'master' into master 2021-05-03 11:47:36 -04:00
567af55a19 make test directory configurable 2021-05-03 18:28:20 +03:00
c6a2e3b4d0 chore: release 1.1.0 2021-05-02 23:41:59 +01:00
a10428efe6 Merge pull request #282 from jordanbrauer/reusable-hooks
Reusable hooks
2021-05-02 19:51:59 +01:00
1440637e41 Add Junit support 2021-05-02 11:22:19 +02:00
14cee66dfd Update README.md 2021-04-20 19:32:33 +01:00
d048d60d04 Ignore the absence of the tests folder 2021-04-10 11:51:13 -04:00
1b9162151c docs: add some method documentation and fix typo 2021-04-07 11:02:00 -05:00
885c9f1f06 test: update the snapshot with new test output 2021-04-07 10:56:19 -05:00
90efcc8a8a feat: add shared/global beforeAll and afterAll hooks 2021-04-07 10:55:19 -05:00
7d35ee9998 feat: add new helper to create static closure chains 2021-04-07 10:53:46 -05:00
584a7ac8a5 test: add cases for global before/after all hooks 2021-04-07 10:52:49 -05:00
f21e45ae64 fix: type declaration for and add small comment RE: merge behaviour 2021-04-07 09:49:53 -05:00
c7d26a27b6 fix: ensure that Pest is loaded before the test file itself 2021-04-07 09:49:12 -05:00
54f9397f47 style: address stan + lint issues 2021-04-06 19:32:49 -05:00
ff44589572 refactor: pack hooks into an array instead of 1 argument per hook 2021-04-06 19:27:50 -05:00
53333b56ab test: adding tests for beforeEach and afterEach + empty tests for *All 2021-04-06 19:26:42 -05:00
6616b6299b docs: update changelog 2021-03-31 16:00:01 +01:00
0bab649156 Merge pull request #284 from owenvoke/feature/update-phpunit
chore(deps): add support for PHPUnit 9.5.4
2021-03-31 15:42:50 +01:00
2de7cb4726 chore(deps): add support for PHPUnit 9.5.4 2021-03-31 15:29:58 +01:00
99500d0cae feat: add shared/global before each hook 2021-03-28 02:03:31 -05:00
7eb5478c42 test: add tests for shared/global before each hooks 2021-03-28 01:54:43 -05:00
d2babb1331 Merge pull request #280 from pristavu/master
Add Dusk command argument --browse
2021-03-25 12:57:46 +00:00
a6cced6b63 Update PestDuskCommand.php
Add Dusk command argument --browse
2021-03-22 01:16:09 +02:00
7917313422 docs: update changelog 2021-03-17 13:44:14 +00:00
e02c22e136 Merge pull request #278 from owenvoke/feature/phpunit-9.5.3
chore(deps): add support for PHPUnit 9.5.3
2021-03-17 13:41:58 +00:00
2157644a39 chore(deps): add support for PHPUnit 9.5.3 2021-03-17 13:38:53 +00:00
b6c2812a91 chore: bump static tests to PHP8.0 2021-03-13 11:14:27 +00:00
4b65d2c426 release: v1.0.3 2021-03-13 11:07:57 +00:00
3589f3d5e7 chore: fixes test suite 2021-03-13 11:06:34 +00:00
1f39b8d239 Merge pull request #269 from jordanbrauer/multiple-suffix-extensions
fix: allow multiple file extensions in test suffix (prevent class & file name syntax errors)
2021-03-13 10:36:02 +00:00
7c3c390cbf chore: bumps dev dependencies 2021-03-12 21:50:23 +00:00
19a1569fa8 cleanup for self-review 2021-02-13 13:31:25 -06:00
9a0240bc7b patch addslashes for windows paths 2021-02-13 12:48:43 -06:00
dd94a843b5 run linter & auto format 2021-02-13 12:14:22 -06:00
9426d08aa2 update snapshot for running tests with failed classnames 2021-02-13 12:12:32 -06:00
fe2fac37f8 address PHPStan warning about use of empty 2021-02-13 11:53:30 -06:00
cabd64df00 rename tests for windows, despite losing some coverage 2021-02-13 11:51:35 -06:00
bb57a54089 simplify quote escape sequence handling 2021-02-13 11:40:08 -06:00
5e0bfba7bf run linter 2021-02-13 11:21:14 -06:00
f6c19e469f in the event of no class name, make one on the fly as an escape hatch 2021-02-13 11:20:16 -06:00
13f09cc662 add helper method to generate a simple random string 2021-02-13 11:19:40 -06:00
a7e2856887 add test for totally invalid PHP class name 2021-02-13 11:19:00 -06:00
721e047485 add another test case with non-standard PHP test file name 2021-02-13 11:18:28 -06:00
301ff155a4 prevent parse errors by escaping the quote used for filename property 2021-02-13 11:17:52 -06:00
40f2065575 catch parse errors and let the user know in a friendlier manner 2021-02-13 11:12:09 -06:00
be906eb823 remove additional str_replace call in favour of adjusting the relative path regexp 2021-02-13 11:08:08 -06:00
2cee825f61 rename test sub directory 2021-02-13 11:07:22 -06:00
ea6308bfdf add tests for vaious file naming conventions resulting in various suffixes 2021-02-13 09:45:41 -06:00
c6a6f7e2ab fix typo 2021-02-13 00:15:54 -06:00
20077c285a WIP proof of concept fix for multiple file extensions in test suffix 2021-02-13 00:14:20 -06:00
fa13016785 docs: update changelog 2021-02-04 09:15:54 +00:00
b81bb9d621 chore(deps): add support for PHPUnit 9.5.2 2021-02-04 09:12:29 +00:00
2deb53c14f docs: fix typo 2021-01-18 09:50:21 +00:00
6b8feed08a docs: update changelog 2021-01-18 09:35:53 +00:00
2fe8e07cf3 Merge pull request #261 from owenvoke/feature/update-phpunit
chore(deps): add support for PHPUnit 9.5.1
2021-01-18 09:31:57 +00:00
d2c907868e chore(deps): add support for PHPUnit 9.5.1 2021-01-18 09:11:38 +00:00
a2900a5e09 Merge pull request #251 from MatanYadaev/fix-expect-phpdoc
Fix `TestCase@expect` phpDoc
2021-01-05 20:19:03 +01:00
df934bacd9 fix TestCase expect phpDoc 2021-01-05 20:42:14 +02:00
b0f03c278d docs: updates changelog 2021-01-03 17:27:11 +01:00
b59b321249 Merge pull request #245 from gpibarra/make-dusk-test
MakeDuskTest
2021-01-02 23:35:32 +01:00
82d6991cf8 docs: updates sponsors 2020-12-28 20:17:23 +01:00
d693d99379 Update src/Laravel/Commands/PestTestCommand.php
Co-authored-by: Owen Voke <development@voke.dev>
2020-12-28 13:51:03 -03:00
50c1136be8 MakeDuskTest 2020-12-28 13:13:30 -03:00
dd491e516c Update FUNDING.yml 2020-12-28 15:54:30 +01:00
078aab0d3d Update FUNDING.yml 2020-12-28 02:00:30 +01:00
89c9f4b428 Update FUNDING.yml 2020-12-28 01:59:56 +01:00
2148e896e2 Update FUNDING.yml 2020-12-28 01:59:30 +01:00
7ba49b2e3e chore: fixes style 2020-12-27 21:42:08 +01:00
54a285f7e3 Update PestInstallCommand.php 2020-12-27 17:40:26 +01:00
3ed20d059c Merge pull request #240 from pestphp/feat/improve-init
feat: improve init files
2020-12-27 16:25:48 +01:00
92b6800f28 feat(improve-init): fixes styleci 2020-12-27 15:39:17 +01:00
29cfd1a2dc feat(improve-init): typos 2020-12-27 15:33:52 +01:00
45c09ea0ed feat(improve-init): wording 2020-12-27 15:28:32 +01:00
424e24d530 feat: improve init files 2020-12-27 15:24:21 +01:00
26b2e3561a docs: update changelog 2020-12-27 11:36:24 +00:00
60aea6798d Merge pull request #239 from gpibarra/patch-1
change binary path pest:dusk
2020-12-27 02:46:08 +01:00
fac3fe3f55 docs: updates changelog 2020-12-26 21:31:38 +01:00
17fac0a488 Update composer.json 2020-12-26 21:19:28 +01:00
6abc2207b2 change binary path pest:dusk
In windows not work
full path is used in [7e05b3ca4f/src/Console/DuskCommand.php (L91)) and [163d5c2bd8/src/Adapters/Laravel/Commands/TestCommand.php (L108))
2020-12-23 15:08:49 -03:00
885d224c5d docs: updates changelog 2020-12-20 17:22:40 +01:00
12441deab8 chore: upgrades version 2020-12-20 17:22:33 +01:00
8852fd14ce Merge pull request #236 from avrahamappel/master
Add test for inheritance with depends
2020-12-20 17:18:53 +01:00
e3814e6d9c chore: fixes static analsysis on uses_classs 2020-12-20 17:12:08 +01:00
e19eba0942 Rebuild snapshots 2020-12-17 23:24:29 -05:00
b6e2763731 Move dependency mapping to TestCase
The `TestCaseFactory::getClassName` was removed since it can have
unexpected results when called too early, as it builds up the test class
prematurely. It is not currently used anywhere else.
2020-12-17 23:24:11 -05:00
f75a3ee865 Add test for inheritance with depends 2020-12-15 22:29:37 -05:00
d707c2f208 Merge pull request #209 from NickSdot/make-root-path-working-in-subdirectory-structures
Get root path based on composer autoloader
2020-12-15 01:58:05 +01:00
ba08f2c11e Changes based on feedback https://github.com/pestphp/pest-intellij/issues/73#issuecomment-709201510 2020-10-15 19:46:16 +08:00
13a8aee049 Get root path from already available and correct path of autoloader 2020-10-14 21:31:28 +08:00
56 changed files with 1148 additions and 214 deletions

30
.gitattributes vendored
View File

@ -1,16 +1,14 @@
/art export-ignore /art export-ignore
/docs export-ignore /docs export-ignore
/tests export-ignore /tests export-ignore
/scripts export-ignore /scripts export-ignore
/.github export-ignore /.github export-ignore
/.php_cs export-ignore /.php-cs-fixer.dist.php export-ignore
.editorconfig export-ignore .editorconfig export-ignore
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore
.travis.yml export-ignore phpstan.neon export-ignore
phpstan.neon export-ignore phpunit.xml export-ignore
rector.yaml export-ignore CHANGELOG.md export-ignore
phpunit.xml export-ignore CONTRIBUTING.md export-ignore
CHANGELOG.md export-ignore README.md export-ignore
CONTRIBUTING.md export-ignore
README.md export-ignore

2
.github/FUNDING.yml vendored
View File

@ -1,5 +1,5 @@
# These are supported funding model platforms # These are supported funding model platforms
github: nunomaduro github: [nunomaduro,owenvoke,olivernybroe,octoper]
patreon: nunomaduro patreon: nunomaduro
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L

View File

@ -15,7 +15,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: 7.4 php-version: 8.0
tools: composer:v2 tools: composer:v2
coverage: none coverage: none
@ -40,7 +40,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: 7.4 php-version: 8.0
tools: composer:v2 tools: composer:v2
coverage: none coverage: none

5
.gitignore vendored
View File

@ -4,8 +4,9 @@ composer.lock
/vendor/ /vendor/
coverage.xml coverage.xml
.phpunit.result.cache .phpunit.result.cache
.php_cs.cache /.php-cs-fixer.php
.php-cs-fixer.cache
.temp/coverage.php .temp/coverage.php
*.swp *.swp
*.swo *.swo
.vscode/ .vscode/

View File

@ -6,7 +6,7 @@ $finder = PhpCsFixer\Finder::create()
->in(__DIR__ . DIRECTORY_SEPARATOR . 'scripts') ->in(__DIR__ . DIRECTORY_SEPARATOR . 'scripts')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs') ->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'src') ->in(__DIR__ . DIRECTORY_SEPARATOR . 'src')
->append(['.php_cs']); ->append(['.php-cs-fixer.dist.php']);
$rules = [ $rules = [
'@Symfony' => true, '@Symfony' => true,
@ -25,7 +25,7 @@ $rules = [
$rules['increment_style'] = ['style' => 'post']; $rules['increment_style'] = ['style' => 'post'];
return PhpCsFixer\Config::create() return (new PhpCsFixer\Config())
->setUsingCache(true) ->setUsingCache(true)
->setRules($rules) ->setRules($rules)
->setFinder($finder); ->setFinder($finder);

View File

@ -4,6 +4,59 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [v1.2.0 (2021-05-13)](https://github.com/pestphp/pest/compare/v1.1.0...v1.2.0)
### Added
- Adds JUnit / Infection support ([#291](https://github.com/pestphp/pest/pull/291))
- `--test-directory` command line option ([#283](https://github.com/pestphp/pest/pull/283))
## [v1.1.0 (2021-05-02)](https://github.com/pestphp/pest/compare/v1.0.5...v1.1.0)
### Added
- Possibility of "hooks" being added using the "uses" function ([#282](https://github.com/pestphp/pest/pull/282))
## [v1.0.5 (2021-03-31)](https://github.com/pestphp/pest/compare/v1.0.4...v1.0.5)
### Added
- Add `--browse` option to `pest:dusk` command ([#280](https://github.com/pestphp/pest/pull/280))
- Support for PHPUnit 9.5.4 ([#284](https://github.com/pestphp/pest/pull/284))
## [v1.0.4 (2021-03-17)](https://github.com/pestphp/pest/compare/v1.0.3...v1.0.4)
### Added
- Support for PHPUnit 9.5.3 ([#278](https://github.com/pestphp/pest/pull/278))
## [v1.0.3 (2021-03-13)](https://github.com/pestphp/pest/compare/v1.0.2...v1.0.3)
### Added
- Support for test extensions ([#269](https://github.com/pestphp/pest/pull/269))
## [v1.0.2 (2021-02-04)](https://github.com/pestphp/pest/compare/v1.0.1...v1.0.2)
### Added
- Support for PHPUnit 9.5.2 ([#267](https://github.com/pestphp/pest/pull/267))
## [v1.0.1 (2021-01-18)](https://github.com/pestphp/pest/compare/v1.0.0...v1.0.1)
### Added
- Support for PHPUnit 9.5.1 ([#261](https://github.com/pestphp/pest/pull/261))
### Fixed
- Fix `TestCase@expect` PHPDoc tag ([#251](https://github.com/pestphp/pest/pull/251))
## [v1.0.0 (2021-01-03)](https://github.com/pestphp/pest/compare/v0.3.19...v1.0.0)
### Added
- `pest:test --dusk` option ([#245](https://github.com/pestphp/pest/pull/245))
### Changed
- Stable version
- Updates init structure ([#240](https://github.com/pestphp/pest/pull/240))
## [v0.3.19 (2020-12-27)](https://github.com/pestphp/pest/compare/v0.3.18...v0.3.19)
### Fixed
- Fix binary path in `pest:dusk` command ([#239](https://github.com/pestphp/pest/pull/239))
## [v0.3.18 (2020-12-26)](https://github.com/pestphp/pest/compare/v0.3.17...v0.3.18)
### Added
- `toBeJson()` expectation ([plugin-expectations#2](https://github.com/pestphp/pest-plugin-expectations/pull/2))
## [v0.3.17 (2020-12-20)](https://github.com/pestphp/pest/compare/v0.3.16...v0.3.17)
### Fixed
- Class inheritance with `depends()` ([#236](https://github.com/pestphp/pest/pull/236))
## [v0.3.16 (2020-12-13)](https://github.com/pestphp/pest/compare/v0.3.15...v0.3.16) ## [v0.3.16 (2020-12-13)](https://github.com/pestphp/pest/compare/v0.3.15...v0.3.16)
### Changed ### Changed
- Moves expectation API for external plugin ([5d7f262](https://github.com/pestphp/pest/commit/5d7f262f4ab280a660a85900f402eebb23abfda8)) - Moves expectation API for external plugin ([5d7f262](https://github.com/pestphp/pest/commit/5d7f262f4ab280a660a85900f402eebb23abfda8))

View File

@ -21,6 +21,8 @@ We would like to extend our thanks to the following sponsors for funding Pest de
### Premium Sponsors ### Premium Sponsors
- **[Scout APM](https://github.com/scoutapp)** - **[Scout APM](https://scoutapm.com)**
- **[Akaunting](https://akaunting.com)**
- **[Meema](https://meema.io/)**
Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**. Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

View File

@ -19,17 +19,21 @@ use Symfony\Component\Console\Output\OutputInterface;
if (file_exists($vendorPath)) { if (file_exists($vendorPath)) {
include_once $vendorPath; include_once $vendorPath;
$autoloadPath = $vendorPath;
} else { } else {
include_once $localPath; include_once $localPath;
$autoloadPath = $localPath;
} }
(new Provider())->register(); (new Provider())->register();
$rootPath = getcwd(); // get $rootPath based on $autoloadPath
$rootPath = dirname($autoloadPath, 2);
$argv = new ArgvInput();
$testSuite = TestSuite::getInstance($rootPath); $testSuite = TestSuite::getInstance($rootPath, $argv->getParameterOption('--test-directory', 'tests'));
$isDecorated = (new ArgvInput())->getParameterOption('--colors', 'always') !== 'never'; $isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never';
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated); $output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated);
$container = Container::getInstance(); $container = Container::getInstance();
@ -38,5 +42,14 @@ use Symfony\Component\Console\Output\OutputInterface;
ValidatesEnvironment::in($testSuite); ValidatesEnvironment::in($testSuite);
// lets remove any arguments that PHPUnit does not understand
if ($argv->hasParameterOption('--test-directory')) {
foreach ($_SERVER['argv'] as $key => $value) {
if (strpos($value, '--test-directory') !== false) {
unset($_SERVER['argv'][$key]);
}
}
}
exit($container->get(Command::class)->run($_SERVER['argv'])); exit($container->get(Command::class)->run($_SERVER['argv']));
})(); })();

View File

@ -19,11 +19,11 @@
"require": { "require": {
"php": "^7.3 || ^8.0", "php": "^7.3 || ^8.0",
"nunomaduro/collision": "^5.0", "nunomaduro/collision": "^5.0",
"pestphp/pest-plugin": "^0.3", "pestphp/pest-plugin": "^1.0",
"pestphp/pest-plugin-coverage": "^0.3", "pestphp/pest-plugin-coverage": "^1.0",
"pestphp/pest-plugin-expectations": "^0.3.2", "pestphp/pest-plugin-expectations": "^1.0",
"pestphp/pest-plugin-init": "^0.3", "pestphp/pest-plugin-init": "^1.0",
"phpunit/phpunit": ">= 9.3.7 <= 9.5.0" "phpunit/phpunit": ">= 9.3.7 <= 9.5.4"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -43,10 +43,10 @@
] ]
}, },
"require-dev": { "require-dev": {
"illuminate/console": "^7.16.1", "illuminate/console": "^8.32.1",
"illuminate/support": "^7.16.1", "illuminate/support": "^8.32.1",
"laravel/dusk": "^6.9.1", "laravel/dusk": "^6.13.0",
"mockery/mockery": "^1.4.1", "mockery/mockery": "^1.4.3",
"pestphp/pest-dev-tools": "dev-master" "pestphp/pest-dev-tools": "dev-master"
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
@ -74,7 +74,7 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "0.4.x-dev" "dev-master": "1.x-dev"
}, },
"pest": { "pest": {
"plugins": [ "plugins": [

View File

@ -14,7 +14,6 @@ parameters:
ignoreErrors: ignoreErrors:
- "#type mixed is not subtype of native#" - "#type mixed is not subtype of native#"
- "#Undefined variable: \\$this#"
- "#is not allowed to extend#" - "#is not allowed to extend#"
- "#Language construct eval#" - "#Language construct eval#"
- "# with null as default value#" - "# with null as default value#"
@ -23,10 +22,13 @@ parameters:
- "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#" - "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#"
- -
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#' message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
path: src/TeamCity.php path: src/Logging
- -
message: '#invalid typehint type Pest\\Concerns\\TestCase#' message: '#invalid typehint type Pest\\Concerns\\TestCase#'
path: src/TeamCity.php path: src/Logging
- -
message: '#is not subtype of native type PHPUnit\\Framework\\Test#' message: '#is not subtype of native type PHPUnit\\Framework\\Test#'
path: src/TeamCity.php path: src/Logging
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getPrintableTestCaseName\(\)#'
path: src/Logging

View File

@ -5,7 +5,8 @@ declare(strict_types=1);
namespace Pest\Actions; namespace Pest\Actions;
use NunoMaduro\Collision\Adapters\Phpunit\Printer; use NunoMaduro\Collision\Adapters\Phpunit\Printer;
use Pest\TeamCity; use Pest\Logging\JUnit;
use Pest\Logging\TeamCity;
use PHPUnit\TextUI\DefaultResultPrinter; use PHPUnit\TextUI\DefaultResultPrinter;
/** /**
@ -32,6 +33,14 @@ final class AddsDefaults
$arguments[self::PRINTER] = new TeamCity($arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS); $arguments[self::PRINTER] = new TeamCity($arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
} }
// Load our junit logger instead.
if (array_key_exists('junitLogfile', $arguments)) {
$arguments['listeners'][] = new JUnit(
$arguments['junitLogfile']
);
unset($arguments['junitLogfile']);
}
return $arguments; return $arguments;
} }
} }

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Pest\Actions; namespace Pest\Actions;
use Pest\Support\Str; use Pest\Support\Str;
use PHPUnit\TextUI\Configuration\Configuration; use function Pest\testDirectory;
use PHPUnit\Util\FileLoader; use PHPUnit\Util\FileLoader;
use RecursiveDirectoryIterator; use RecursiveDirectoryIterator;
use RecursiveIteratorIterator; use RecursiveIteratorIterator;
@ -33,7 +33,7 @@ final class LoadStructure
*/ */
public static function in(string $rootPath): void public static function in(string $rootPath): void
{ {
$testsPath = $rootPath . DIRECTORY_SEPARATOR . 'tests'; $testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory();
$load = function ($filename): bool { $load = function ($filename): bool {
return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename); return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename);

View File

@ -5,9 +5,10 @@ declare(strict_types=1);
namespace Pest\Concerns; namespace Pest\Concerns;
use Closure; use Closure;
use Pest\Support\ChainableClosure;
use Pest\Support\ExceptionTrace; use Pest\Support\ExceptionTrace;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Util\Test; use PHPUnit\Framework\ExecutionOrderDependency;
use Throwable; use Throwable;
/** /**
@ -33,6 +34,38 @@ trait TestCase
*/ */
private $__test; private $__test;
/**
* Holds a global/shared beforeEach ("set up") closure if one has been
* defined.
*
* @var Closure|null
*/
private $beforeEach = null;
/**
* Holds a global/shared afterEach ("tear down") closure if one has been
* defined.
*
* @var Closure|null
*/
private $afterEach = null;
/**
* Holds a global/shared beforeAll ("set up before") closure if one has been
* defined.
*
* @var Closure|null
*/
private static $beforeAll = null;
/**
* Holds a global/shared afterAll ("tear down after") closure if one has
* been defined.
*
* @var Closure|null
*/
private static $afterAll = null;
/** /**
* Creates a new instance of the test case. * Creates a new instance of the test case.
*/ */
@ -54,6 +87,86 @@ trait TestCase
$this->setGroups($groups); $this->setGroups($groups);
} }
/**
* Add dependencies to the test case and map them to instances of ExecutionOrderDependency.
*/
public function addDependencies(array $tests): void
{
$className = get_class($this);
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) {
$test = "{$className}::{$test}";
}
return new ExecutionOrderDependency($test, null, '');
}, $tests);
$this->setDependencies($tests);
}
/**
* Add a shared/"global" before all test hook that will execute **before**
* the test defined `beforeAll` hook(s).
*/
public function addBeforeAll(?Closure $hook): void
{
if (!$hook) {
return;
}
self::$beforeAll = (self::$beforeAll instanceof Closure)
? ChainableClosure::fromStatic(self::$beforeAll, $hook)
: $hook;
}
/**
* Add a shared/"global" after all test hook that will execute **before**
* the test defined `afterAll` hook(s).
*/
public function addAfterAll(?Closure $hook): void
{
if (!$hook) {
return;
}
self::$afterAll = (self::$afterAll instanceof Closure)
? ChainableClosure::fromStatic(self::$afterAll, $hook)
: $hook;
}
/**
* Add a shared/"global" before each test hook that will execute **before**
* the test defined `beforeEach` hook.
*/
public function addBeforeEach(?Closure $hook): void
{
$this->addHook('beforeEach', $hook);
}
/**
* Add a shared/"global" after each test hook that will execute **before**
* the test defined `afterEach` hook.
*/
public function addAfterEach(?Closure $hook): void
{
$this->addHook('afterEach', $hook);
}
/**
* Add a shared/global hook and compose them if more than one is passed.
*/
private function addHook(string $property, ?Closure $hook): void
{
if (!$hook) {
return;
}
$this->{$property} = ($this->{$property} instanceof Closure)
? ChainableClosure::from($this->{$property}, $hook)
: $hook;
}
/** /**
* Returns the test case name. Note that, in Pest * Returns the test case name. Note that, in Pest
* we ignore withDataset argument as the description * we ignore withDataset argument as the description
@ -78,6 +191,10 @@ trait TestCase
$beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename); $beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename);
if (self::$beforeAll instanceof Closure) {
$beforeAll = ChainableClosure::fromStatic(self::$beforeAll, $beforeAll);
}
call_user_func(Closure::bind($beforeAll, null, self::class)); call_user_func(Closure::bind($beforeAll, null, self::class));
} }
@ -88,6 +205,10 @@ trait TestCase
{ {
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename); $afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
if (self::$afterAll instanceof Closure) {
$afterAll = ChainableClosure::fromStatic(self::$afterAll, $afterAll);
}
call_user_func(Closure::bind($afterAll, null, self::class)); call_user_func(Closure::bind($afterAll, null, self::class));
parent::tearDownAfterClass(); parent::tearDownAfterClass();
@ -104,6 +225,10 @@ trait TestCase
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
if ($this->beforeEach instanceof Closure) {
$beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach);
}
$this->__callClosure($beforeEach, func_get_args()); $this->__callClosure($beforeEach, func_get_args());
} }
@ -114,6 +239,10 @@ trait TestCase
{ {
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
if ($this->afterEach instanceof Closure) {
$afterEach = ChainableClosure::from($this->afterEach, $afterEach);
}
$this->__callClosure($afterEach, func_get_args()); $this->__callClosure($afterEach, func_get_args());
parent::tearDown(); parent::tearDown();

View File

@ -90,8 +90,6 @@ final class Command extends BaseCommand
*/ */
$this->arguments = AddsDefaults::to($this->arguments); $this->arguments = AddsDefaults::to($this->arguments);
LoadStructure::in($this->testSuite->rootPath);
$testRunner = new TestRunner($this->arguments['loader']); $testRunner = new TestRunner($this->arguments['loader']);
$testSuite = $this->arguments['test']; $testSuite = $this->arguments['test'];
@ -127,6 +125,8 @@ final class Command extends BaseCommand
*/ */
public function run(array $argv, bool $exit = true): int public function run(array $argv, bool $exit = true): int
{ {
LoadStructure::in($this->testSuite->rootPath);
$result = parent::run($argv, false); $result = parent::run($argv, false);
/* /*

View File

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* @internal
*/
final class InvalidUsesPath extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new instance of invalid uses path.
*/
public function __construct(string $target)
{
parent::__construct(sprintf('The path `%s` is not valid.', $target));
}
}

View File

@ -5,14 +5,17 @@ declare(strict_types=1);
namespace Pest\Factories; namespace Pest\Factories;
use Closure; use Closure;
use ParseError;
use Pest\Concerns; use Pest\Concerns;
use Pest\Contracts\HasPrintableTestCaseName; use Pest\Contracts\HasPrintableTestCaseName;
use Pest\Datasets; use Pest\Datasets;
use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\ShouldNotHappen;
use Pest\Support\HigherOrderMessageCollection; use Pest\Support\HigherOrderMessageCollection;
use Pest\Support\NullClosure; use Pest\Support\NullClosure;
use Pest\Support\Str;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use RuntimeException;
/** /**
* @internal * @internal
@ -139,6 +142,7 @@ final class TestCaseFactory
$proxies->proxy($this); $proxies->proxy($this);
$chains->chain($this); $chains->chain($this);
/* @phpstan-ignore-next-line */
return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args()); return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args());
}; };
@ -156,14 +160,6 @@ final class TestCaseFactory
return array_map($createTest, array_keys($datasets), $datasets); return array_map($createTest, array_keys($datasets), $datasets);
} }
/**
* Makes a fully qualified class name from the current filename.
*/
public function getClassName(): string
{
return $this->makeClassFromFilename($this->filename);
}
/** /**
* Makes a fully qualified class name from the given filename. * Makes a fully qualified class name from the given filename.
*/ */
@ -176,7 +172,7 @@ final class TestCaseFactory
}, $filename); }, $filename);
} }
$filename = (string) realpath($filename); $filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
$rootPath = TestSuite::getInstance()->rootPath; $rootPath = TestSuite::getInstance()->rootPath;
$relativePath = str_replace($rootPath . DIRECTORY_SEPARATOR, '', $filename); $relativePath = str_replace($rootPath . DIRECTORY_SEPARATOR, '', $filename);
$relativePath = dirname(ucfirst($relativePath)) . DIRECTORY_SEPARATOR . basename($relativePath, '.php'); $relativePath = dirname(ucfirst($relativePath)) . DIRECTORY_SEPARATOR . basename($relativePath, '.php');
@ -184,8 +180,12 @@ final class TestCaseFactory
// Strip out any %-encoded octets. // Strip out any %-encoded octets.
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath); $relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
// Remove escaped quote sequences (maintain namespace)
$relativePath = str_replace(array_map(function (string $quote): string {
return sprintf('\\%s', $quote);
}, ['\'', '"']), '', $relativePath);
// Limit to A-Z, a-z, 0-9, '_', '-'. // Limit to A-Z, a-z, 0-9, '_', '-'.
$relativePath = (string) preg_replace('/[^A-Za-z0-9.\\\]/', '', $relativePath); $relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
$classFQN = 'P\\' . $relativePath; $classFQN = 'P\\' . $relativePath;
if (class_exists($classFQN)) { if (class_exists($classFQN)) {
@ -202,15 +202,24 @@ final class TestCaseFactory
$namespace = implode('\\', $partsFQN); $namespace = implode('\\', $partsFQN);
$baseClass = sprintf('\%s', $this->class); $baseClass = sprintf('\%s', $this->class);
eval(" if ('' === trim($className)) {
namespace $namespace; $className = 'InvalidTestName' . Str::random();
$classFQN .= $className;
}
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN { try {
$traitsCode eval("
namespace $namespace;
private static \$__filename = '$filename'; final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
} $traitsCode
");
private static \$__filename = '$filename';
}
");
} catch (ParseError $caught) {
throw new RuntimeException(sprintf('Unable to create test case for test file at %s', $filename), 1, $caught);
}
return $classFQN; return $classFQN;
} }

View File

@ -8,6 +8,7 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Pest\Exceptions\InvalidConsoleArgument; use Pest\Exceptions\InvalidConsoleArgument;
use function Pest\testDirectory;
/** /**
* @internal * @internal
@ -36,7 +37,7 @@ final class PestDatasetCommand extends Command
/** @var string $name */ /** @var string $name */
$name = $this->argument('name'); $name = $this->argument('name');
$relativePath = sprintf('tests/Datasets/%s.php', ucfirst($name)); $relativePath = sprintf(testDirectory('Datasets/%s.php'), ucfirst($name));
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
$target = base_path($relativePath); $target = base_path($relativePath);

View File

@ -16,7 +16,9 @@ final class PestDuskCommand extends DuskCommand
* *
* @var string * @var string
*/ */
protected $signature = 'pest:dusk {--without-tty : Disable output to TTY}'; protected $signature = 'pest:dusk
{--browse : Open a browser instead of using headless mode}
{--without-tty : Disable output to TTY}';
/** /**
* The console command description. * The console command description.
@ -33,9 +35,9 @@ final class PestDuskCommand extends DuskCommand
protected function binary() protected function binary()
{ {
if ('phpdbg' === PHP_SAPI) { if ('phpdbg' === PHP_SAPI) {
return [PHP_BINARY, '-qrr', 'vendor/bin/pest']; return [PHP_BINARY, '-qrr', 'vendor/pestphp/pest/bin/pest'];
} }
return [PHP_BINARY, 'vendor/bin/pest']; return [PHP_BINARY, 'vendor/pestphp/pest/bin/pest'];
} }
} }

View File

@ -8,7 +8,7 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Pest\Console\Thanks; use Pest\Console\Thanks;
use Pest\Exceptions\InvalidConsoleArgument; use Pest\Exceptions\InvalidConsoleArgument;
use Pest\Support\Str; use function Pest\testDirectory;
/** /**
* @internal * @internal
@ -35,15 +35,11 @@ final class PestInstallCommand extends Command
public function handle(): void public function handle(): void
{ {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
$pest = base_path('tests/Pest.php'); $pest = base_path(testDirectory('Pest.php'));
/* @phpstan-ignore-next-line */ $stubs = 'stubs/Laravel';
$helpers = base_path('tests/Helpers.php');
$stubs = $this->isLumen() ? 'stubs/Lumen' : 'stubs/Laravel';
foreach ([$pest, $helpers] as $file) { if (File::exists($pest)) {
if (File::exists($file)) { throw new InvalidConsoleArgument(sprintf('%s already exist', $pest));
throw new InvalidConsoleArgument(sprintf('%s already exist', $file));
}
} }
File::copy(implode(DIRECTORY_SEPARATOR, [ File::copy(implode(DIRECTORY_SEPARATOR, [
@ -52,26 +48,10 @@ final class PestInstallCommand extends Command
'Pest.php', 'Pest.php',
]), $pest); ]), $pest);
File::copy(implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__, 3),
$stubs,
'Helpers.php',
]), $helpers);
$this->output->success('`tests/Pest.php` created successfully.'); $this->output->success('`tests/Pest.php` created successfully.');
$this->output->success('`tests/Helpers.php` created successfully.');
if (!(bool) $this->option('no-interaction')) { if (!(bool) $this->option('no-interaction')) {
(new Thanks($this->output))(); (new Thanks($this->output))();
} }
} }
/**
* Determine if this is a Lumen application.
*/
private function isLumen(): bool
{
/* @phpstan-ignore-next-line */
return Str::startsWith(app()->version(), 'Lumen');
}
} }

View File

@ -8,6 +8,7 @@ use Illuminate\Console\Command;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Pest\Exceptions\InvalidConsoleArgument; use Pest\Exceptions\InvalidConsoleArgument;
use Pest\Support\Str; use Pest\Support\Str;
use function Pest\testDirectory;
/** /**
* @internal * @internal
@ -19,7 +20,7 @@ final class PestTestCommand extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test}'; protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test}';
/** /**
* The console command description. * The console command description.
@ -36,9 +37,9 @@ final class PestTestCommand extends Command
/** @var string $name */ /** @var string $name */
$name = $this->argument('name'); $name = $this->argument('name');
$type = ((bool) $this->option('unit')) ? 'Unit' : 'Feature'; $type = ((bool) $this->option('unit')) ? 'Unit' : (((bool) $this->option('dusk')) ? 'Browser' : 'Feature');
$relativePath = sprintf('tests/%s/%s.php', $relativePath = sprintf(testDirectory('%s/%s.php'),
$type, $type,
ucfirst($name) ucfirst($name)
); );

430
src/Logging/JUnit.php Normal file
View File

@ -0,0 +1,430 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Pest\Logging;
use function class_exists;
use DOMDocument;
use DOMElement;
use Exception;
use function get_class;
use function method_exists;
use Pest\Concerns\TestCase;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExceptionWrapper;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Filter;
use PHPUnit\Util\Printer;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
use function sprintf;
use function str_replace;
use Throwable;
use function trim;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class JUnit extends Printer implements TestListener
{
/**
* @var DOMDocument
*/
private $document;
/**
* @var DOMElement
*/
private $root;
/**
* @var DOMElement[]
*/
private $testSuites = [];
/**
* @var int[]
*/
private $testSuiteTests = [0];
/**
* @var int[]
*/
private $testSuiteAssertions = [0];
/**
* @var int[]
*/
private $testSuiteErrors = [0];
/**
* @var int[]
*/
private $testSuiteWarnings = [0];
/**
* @var int[]
*/
private $testSuiteFailures = [0];
/**
* @var int[]
*/
private $testSuiteSkipped = [0];
/**
* @var int[]|float[]
*/
private $testSuiteTimes = [0];
/**
* @var int
*/
private $testSuiteLevel = 0;
/**
* @var DOMElement|null
*/
private $currentTestCase;
public function __construct(string $out)
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('testsuites');
$this->document->appendChild($this->root);
parent::__construct($out);
}
/**
* Flush buffer and close output.
*/
public function flush(): void
{
$this->write($this->getXML());
parent::flush();
}
/**
* An error occurred.
*/
public function addError(Test $test, Throwable $t, float $time): void
{
$this->doAddFault($test, $t, 'error');
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->doAddFault($test, $e, 'warning');
$this->testSuiteWarnings[$this->testSuiteLevel]++;
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->doAddFault($test, $e, 'failure');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
$this->doAddSkipped();
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, Throwable $t, float $time): void
{
$this->doAddSkipped();
}
/** @phpstan-ignore-next-line */
public function startTestSuite(TestSuite $suite): void
{
$testSuite = $this->document->createElement('testsuite');
$testSuite->setAttribute('name', $suite->getName());
if (class_exists($suite->getName(), false)) {
try {
$class = new ReflectionClass($suite->getName());
if ($class->hasMethod('__getFileName')) {
$fileName = $class->getMethod('__getFileName')->invoke(null);
} else {
$fileName = $class->getFileName();
}
$testSuite->setAttribute('file', $fileName);
} catch (ReflectionException $e) {
// @ignoreException
}
}
if ($this->testSuiteLevel > 0) {
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
} else {
$this->root->appendChild($testSuite);
}
$this->testSuiteLevel++;
$this->testSuites[$this->testSuiteLevel] = $testSuite;
$this->testSuiteTests[$this->testSuiteLevel] = 0;
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
$this->testSuiteWarnings[$this->testSuiteLevel] = 0;
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
/** @phpstan-ignore-next-line */
public function endTestSuite(TestSuite $suite): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'tests',
(string) $this->testSuiteTests[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'assertions',
(string) $this->testSuiteAssertions[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'errors',
(string) $this->testSuiteErrors[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'warnings',
(string) $this->testSuiteWarnings[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'failures',
(string) $this->testSuiteFailures[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'skipped',
(string) $this->testSuiteSkipped[$this->testSuiteLevel]
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'time',
sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
);
if ($this->testSuiteLevel > 1) {
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
$this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel];
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
}
$this->testSuiteLevel--;
}
/**
* A test started.
*
* @param Test|TestCase $test
*/
public function startTest(Test $test): void
{
$usesDataprovider = false;
if (method_exists($test, 'usesDataProvider')) {
$usesDataprovider = $test->usesDataProvider();
}
$testCase = $this->document->createElement('testcase');
$testCase->setAttribute('name', $test->getName());
try {
$class = new ReflectionClass($test);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
$methodName = $test->getName(!$usesDataprovider);
if ($class->hasMethod($methodName)) {
try {
$method = $class->getMethod($methodName);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
// @phpstan-ignore-next-line
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
$testCase->setAttribute('class', $class->getName());
$testCase->setAttribute('classname', str_replace('\\', '.', $class->getName()));
$fileName = $class->getFileName();
if ($fileName !== false) {
$testCase->setAttribute('file', $fileName);
}
$testCase->setAttribute('line', (string) $method->getStartLine());
}
if (TeamCity::isPestTest($test)) {
$testCase->setAttribute('class', $test->getPrintableTestCaseName());
$testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName()));
// @phpstan-ignore-next-line
$testCase->setAttribute('file', $test->__getFileName());
}
$this->currentTestCase = $testCase;
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
$numAssertions = 0;
if (method_exists($test, 'getNumAssertions')) {
$numAssertions = $test->getNumAssertions();
}
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
if ($this->currentTestCase !== null) {
$this->currentTestCase->setAttribute(
'assertions',
(string) $numAssertions
);
$this->currentTestCase->setAttribute(
'time',
sprintf('%F', $time)
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase
);
}
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$testOutput = '';
if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) {
$testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
}
if ($testOutput !== '') {
$systemOut = $this->document->createElement(
'system-out',
Xml::prepareString($testOutput)
);
if ($this->currentTestCase !== null) {
$this->currentTestCase->appendChild($systemOut);
}
}
$this->currentTestCase = null;
}
/**
* Returns the XML as a string.
*/
public function getXML(): string
{
$xml = $this->document->saveXML();
if ($xml === false) {
return '';
}
return $xml;
}
private function doAddFault(Test $test, Throwable $t, string $type): void
{
if ($this->currentTestCase === null) {
return;
}
if ($test instanceof SelfDescribing) {
$buffer = $test->toString() . "\n";
} else {
$buffer = '';
}
$buffer .= trim(
TestFailure::exceptionToString($t) . "\n" .
Filter::getFilteredStacktrace($t)
);
$fault = $this->document->createElement(
$type,
Xml::prepareString($buffer)
);
if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName());
} else {
$fault->setAttribute('type', get_class($t));
}
$this->currentTestCase->appendChild($fault);
}
private function doAddSkipped(): void
{
if ($this->currentTestCase === null) {
return;
}
$skipped = $this->document->createElement('skipped');
$this->currentTestCase->appendChild($skipped);
$this->testSuiteSkipped[$this->testSuiteLevel]++;
}
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace Pest; namespace Pest\Logging;
use function getmypid; use function getmypid;
use Pest\Concerns\TestCase; use Pest\Concerns\TestCase;
@ -121,7 +121,7 @@ final class TeamCity extends DefaultResultPrinter
$this->printEvent('testStarted', [ $this->printEvent('testStarted', [
self::NAME => $test->getName(), self::NAME => $test->getName(),
/* @phpstan-ignore-next-line */ // @phpstan-ignore-next-line
self::LOCATION_HINT => self::PROTOCOL . $test->toString(), self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
]); ]);
} }
@ -203,8 +203,11 @@ final class TeamCity extends DefaultResultPrinter
return (int) round($time * 1000); return (int) round($time * 1000);
} }
private static function isPestTest(Test $test): bool public static function isPestTest(Test $test): bool
{ {
return in_array(TestCase::class, class_uses($test), true); /** @var array<string, string> $uses */
$uses = class_uses($test);
return in_array(TestCase::class, $uses, true);
} }
} }

View File

@ -9,11 +9,10 @@ use Pest\Factories\TestCaseFactory;
use Pest\Support\Backtrace; use Pest\Support\Backtrace;
use Pest\Support\NullClosure; use Pest\Support\NullClosure;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\ExecutionOrderDependency;
use SebastianBergmann\Exporter\Exporter; use SebastianBergmann\Exporter\Exporter;
/** /**
* @method \Pest\Expectation expect(mixed $value) * @method \Pest\Expectations\Expectation expect(mixed $value)
* *
* @internal * @internal
*/ */
@ -92,19 +91,9 @@ final class TestCall
*/ */
public function depends(string ...$tests): TestCall public function depends(string ...$tests): TestCall
{ {
$className = $this->testCaseFactory->getClassName();
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) {
$test = "{$className}::{$test}";
}
return new ExecutionOrderDependency($test, null, '');
}, $tests);
$this->testCaseFactory $this->testCaseFactory
->factoryProxies ->factoryProxies
->add(Backtrace::file(), Backtrace::line(), 'setDependencies', [$tests]); ->add(Backtrace::file(), Backtrace::line(), 'addDependencies', [$tests]);
return $this; return $this;
} }

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Pest\PendingObjects; namespace Pest\PendingObjects;
use Pest\Exceptions\InvalidUsesPath; use Closure;
use Pest\TestSuite; use Pest\TestSuite;
/** /**
@ -12,6 +12,20 @@ use Pest\TestSuite;
*/ */
final class UsesCall final class UsesCall
{ {
/**
* Contains a global before each hook closure to be executed.
*
* Array indices here matter. They are mapped as follows:
*
* - `0` => `beforeAll`
* - `1` => `beforeEach`
* - `2` => `afterEach`
* - `3` => `afterAll`
*
* @var array<int, Closure>
*/
private $hooks = [];
/** /**
* Holds the class and traits. * Holds the class and traits.
* *
@ -77,14 +91,13 @@ final class UsesCall
]); ]);
}, $targets); }, $targets);
$this->targets = array_map(function ($target): string { $this->targets = array_reduce($targets, function (array $accumulator, string $target): array {
$isValid = is_dir($target) || file_exists($target); if (is_dir($target) || file_exists($target)) {
if (!$isValid) { $accumulator[] = (string) realpath($target);
throw new InvalidUsesPath($target);
} }
return (string) realpath($target); return $accumulator;
}, $targets); }, []);
} }
/** /**
@ -97,11 +110,56 @@ final class UsesCall
return $this; return $this;
} }
/**
* Sets the global beforeAll test hook.
*/
public function beforeAll(Closure $hook): UsesCall
{
$this->hooks[0] = $hook;
return $this;
}
/**
* Sets the global beforeEach test hook.
*/
public function beforeEach(Closure $hook): UsesCall
{
$this->hooks[1] = $hook;
return $this;
}
/**
* Sets the global afterEach test hook.
*/
public function afterEach(Closure $hook): UsesCall
{
$this->hooks[2] = $hook;
return $this;
}
/**
* Sets the global afterAll test hook.
*/
public function afterAll(Closure $hook): UsesCall
{
$this->hooks[3] = $hook;
return $this;
}
/** /**
* Dispatch the creation of uses. * Dispatch the creation of uses.
*/ */
public function __destruct() public function __destruct()
{ {
TestSuite::getInstance()->tests->use($this->classAndTraits, $this->groups, $this->targets); TestSuite::getInstance()->tests->use(
$this->classAndTraits,
$this->groups,
$this->targets,
$this->hooks,
);
} }
} }

View File

@ -6,5 +6,10 @@ namespace Pest;
function version(): string function version(): string
{ {
return '0.3.15'; return '1.2.0';
}
function testDirectory(string $file = ''): string
{
return TestSuite::getInstance()->testPath . '/' . $file;
} }

View File

@ -22,7 +22,7 @@ final class Plugin
public static function uses(string ...$traits): void public static function uses(string ...$traits): void
{ {
self::$callables[] = function () use ($traits): void { self::$callables[] = function () use ($traits): void {
uses(...$traits)->in(TestSuite::getInstance()->rootPath . DIRECTORY_SEPARATOR . 'tests'); uses(...$traits)->in(TestSuite::getInstance()->rootPath . DIRECTORY_SEPARATOR . testDirectory());
}; };
} }
} }

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Pest\Repositories; namespace Pest\Repositories;
use Closure;
use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\ShouldNotHappen;
use Pest\Exceptions\TestAlreadyExist; use Pest\Exceptions\TestAlreadyExist;
use Pest\Exceptions\TestCaseAlreadyInUse; use Pest\Exceptions\TestCaseAlreadyInUse;
@ -24,7 +25,7 @@ final class TestRepository
private $state = []; private $state = [];
/** /**
* @var array<string, array<int, array<int, string>>> * @var array<string, array<int, array<int, string|Closure>>>
*/ */
private $uses = []; private $uses = [];
@ -46,12 +47,13 @@ final class TestRepository
}; };
foreach ($this->uses as $path => $uses) { foreach ($this->uses as $path => $uses) {
[$classOrTraits, $groups] = $uses; [$classOrTraits, $groups, $hooks] = $uses;
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith): void {
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $hooks): void {
[$filename] = explode('@', $key); [$filename] = explode('@', $key);
if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) { if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) {
foreach ($classOrTraits as $class) { foreach ($classOrTraits as $class) { /** @var string $class */
if (class_exists($class)) { if (class_exists($class)) {
if ($testCase->class !== TestCase::class) { if ($testCase->class !== TestCase::class) {
throw new TestCaseAlreadyInUse($testCase->class, $class, $filename); throw new TestCaseAlreadyInUse($testCase->class, $class, $filename);
@ -62,10 +64,12 @@ final class TestRepository
} }
} }
$testCase // IDEA: Consider set the real lines on these.
->factoryProxies $testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]);
// Consider set the real line here. $testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]);
->add($filename, 0, 'addGroups', [$groups]); $testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$hooks[1] ?? null]);
$testCase->factoryProxies->add($filename, 0, 'addAfterEach', [$hooks[2] ?? null]);
$testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]);
} }
}; };
@ -81,7 +85,7 @@ final class TestRepository
$state = count($onlyState) > 0 ? $onlyState : $this->state; $state = count($onlyState) > 0 ? $onlyState : $this->state;
foreach ($state as $testFactory) { foreach ($state as $testFactory) {
/* @var TestCaseFactory $testFactory */ /** @var TestCaseFactory $testFactory */
$tests = $testFactory->build($testSuite); $tests = $testFactory->build($testSuite);
foreach ($tests as $test) { foreach ($tests as $test) {
$each($test); $each($test);
@ -92,11 +96,12 @@ final class TestRepository
/** /**
* Uses the given `$testCaseClass` on the given `$paths`. * Uses the given `$testCaseClass` on the given `$paths`.
* *
* @param array<int, string> $classOrTraits * @param array<int, string> $classOrTraits
* @param array<int, string> $groups * @param array<int, string> $groups
* @param array<int, string> $paths * @param array<int, string> $paths
* @param array<int, Closure> $hooks
*/ */
public function use(array $classOrTraits, array $groups, array $paths): void public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void
{ {
foreach ($classOrTraits as $classOrTrait) { foreach ($classOrTraits as $classOrTrait) {
if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) { if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) {
@ -109,9 +114,10 @@ final class TestRepository
$this->uses[$path] = [ $this->uses[$path] = [
array_merge($this->uses[$path][0], $classOrTraits), array_merge($this->uses[$path][0], $classOrTraits),
array_merge($this->uses[$path][1], $groups), array_merge($this->uses[$path][1], $groups),
$this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices
]; ];
} else { } else {
$this->uses[$path] = [$classOrTraits, $groups]; $this->uses[$path] = [$classOrTraits, $groups, $hooks];
} }
} }
} }

View File

@ -12,13 +12,28 @@ use Closure;
final class ChainableClosure final class ChainableClosure
{ {
/** /**
* Calls the given `$closure` and chains the the `$next` closure. * Calls the given `$closure` and chains the `$next` closure.
*/ */
public static function from(Closure $closure, Closure $next): Closure public static function from(Closure $closure, Closure $next): Closure
{ {
return function () use ($closure, $next): void { return function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, $this, get_class($this)), func_get_args()); call_user_func_array(Closure::bind($closure, $this, get_class($this)), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args()); call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args());
}; };
} }
/**
* Call the given static `$closure` and chains the `$next` closure.
*/
public static function fromStatic(Closure $closure, Closure $next): Closure
{
return static function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, null, self::class), func_get_args());
/* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, null, self::class), func_get_args());
};
}
} }

View File

@ -79,6 +79,7 @@ final class Container
if ($candidate === null) { if ($candidate === null) {
$type = $param->getType(); $type = $param->getType();
/* @phpstan-ignore-next-line */
if ($type !== null && $type->isBuiltin()) { if ($type !== null && $type->isBuiltin()) {
$candidate = $param->getName(); $candidate = $param->getName();
} else { } else {

View File

@ -12,7 +12,6 @@ use ReflectionException;
use ReflectionFunction; use ReflectionFunction;
use ReflectionNamedType; use ReflectionNamedType;
use ReflectionParameter; use ReflectionParameter;
use ReflectionProperty;
/** /**
* @internal * @internal

View File

@ -9,6 +9,25 @@ namespace Pest\Support;
*/ */
final class Str final class Str
{ {
/**
* Pool of alpha-numeric characters for generating (unsafe) random strings
* from.
*/
private const POOL = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* Create a (unsecure & non-cryptographically safe) random alpha-numeric
* string value.
*
* @param int $length the length of the resulting randomized string
*
* @see https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Str.php#L240-L242
*/
public static function random(int $length = 16): string
{
return substr(str_shuffle(str_repeat(self::POOL, 5)), 0, $length);
}
/** /**
* Checks if the given `$target` starts with the given `$search`. * Checks if the given `$target` starts with the given `$search`.
*/ */

View File

@ -66,6 +66,13 @@ final class TestSuite
*/ */
public $rootPath; public $rootPath;
/**
* Holds the test path.
*
* @var string
*/
public $testPath;
/** /**
* Holds an instance of the test suite. * Holds an instance of the test suite.
* *
@ -76,7 +83,7 @@ final class TestSuite
/** /**
* Creates a new instance of the test suite. * Creates a new instance of the test suite.
*/ */
public function __construct(string $rootPath) public function __construct(string $rootPath, string $testPath)
{ {
$this->beforeAll = new BeforeAllRepository(); $this->beforeAll = new BeforeAllRepository();
$this->beforeEach = new BeforeEachRepository(); $this->beforeEach = new BeforeEachRepository();
@ -85,15 +92,16 @@ final class TestSuite
$this->afterAll = new AfterAllRepository(); $this->afterAll = new AfterAllRepository();
$this->rootPath = (string) realpath($rootPath); $this->rootPath = (string) realpath($rootPath);
$this->testPath = $testPath;
} }
/** /**
* Returns the current instance of the test suite. * Returns the current instance of the test suite.
*/ */
public static function getInstance(string $rootPath = null): TestSuite public static function getInstance(string $rootPath = null, string $testPath = null): TestSuite
{ {
if (is_string($rootPath)) { if (is_string($rootPath) && is_string($testPath)) {
self::$instance = new TestSuite($rootPath); self::$instance = new TestSuite($rootPath, $testPath);
foreach (Plugin::$callables as $callable) { foreach (Plugin::$callables as $callable) {
$callable(); $callable();

10
stubs/Browser.php Normal file
View File

@ -0,0 +1,10 @@
<?php
use Laravel\Dusk\Browser;
it('has {name} page', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/{name}')
->assertSee('{name}');
});
});

View File

@ -1,11 +0,0 @@
<?php
namespace Tests;
/**
* A basic assert example.
*/
function assertExample(): void
{
test()->assertTrue(true);
}

View File

@ -1,3 +1,45 @@
<?php <?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
uses(Tests\TestCase::class)->in('Feature'); uses(Tests\TestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
function something()
{
// ..
}

View File

@ -1,11 +0,0 @@
<?php
namespace Tests;
/**
* A basic assert example.
*/
function assertExample(): void
{
test()->assertTrue(true);
}

View File

@ -1,3 +0,0 @@
<?php
uses(TestCase::class)->in(__DIR__);

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
</phpunit>

View File

@ -103,6 +103,39 @@
PASS Tests\Fixtures\ExampleTest PASS Tests\Fixtures\ExampleTest
✓ it example 2 ✓ it example 2
PASS Tests\Hooks\AfterAllTest
✓ global afterAll execution order
PASS Tests\Hooks\AfterEachTest
✓ global afterEach execution order
PASS Tests\Hooks\BeforeAllTest
✓ global beforeAll execution order
PASS Tests\Hooks\BeforeEachTest
✓ global beforeEach execution order
PASS Tests\PHPUnit\CustomAffixes\InvalidTestName
✓ it runs file names like `@#$%^&()-_=+.php`
PASS Tests\PHPUnit\CustomAffixes\ATestWithSpaces
✓ it runs file names like `A Test With Spaces.php`
PASS Tests\PHPUnit\CustomAffixes\AdditionalFileExtensionspec
✓ it runs file names like `AdditionalFileExtension.spec.php`
PASS Tests\PHPUnit\CustomAffixes\ManyExtensionsclasstest
✓ it runs file names like `ManyExtensions.class.test.php`
PASS Tests\PHPUnit\CustomAffixes\TestCaseWithQuotes
✓ it runs file names like `Test 'Case' With Quotes.php`
PASS Tests\PHPUnit\CustomAffixes\kebabcasespec
✓ it runs file names like `kebab-case-spec.php`
PASS Tests\PHPUnit\CustomAffixes\snakecasespec
✓ it runs file names like `snake_case_spec.php`
PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory
✓ closure was bound to CustomTestCase ✓ closure was bound to CustomTestCase
@ -184,5 +217,9 @@
✓ depends run test only once ✓ depends run test only once
✓ depends works with the correct test name ✓ depends works with the correct test name
Tests: 7 skipped, 106 passed PASS Tests\Features\DependsInheritance
✓ it is a test
✓ it uses correct parent class
Tests: 7 skipped, 119 passed

View File

@ -0,0 +1,22 @@
<?php
use PHPUnit\Framework\TestCase;
class InheritanceTest extends TestCase
{
public function foo()
{
return 'bar';
}
}
uses(InheritanceTest::class);
it('is a test', function () {
expect(true)->toBeTrue();
});
it('uses correct parent class', function () {
expect(get_parent_class($this))->toEqual(InheritanceTest::class);
expect($this->foo())->toEqual('bar');
})->depends('it is a test');

View File

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

View File

@ -0,0 +1,23 @@
<?php
uses()->afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(0);
$this->ith = 1;
});
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(1);
});
test('global afterEach execution order', function () {
expect($this)
->not()
->toHaveProperty('ith');
});

View File

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

View File

@ -0,0 +1,26 @@
<?php
uses()->beforeEach(function () {
expect($this)
->toHaveProperty('baz')
->and($this->baz)
->toBe(0);
$this->baz = 1;
});
beforeEach(function () {
expect($this)
->toHaveProperty('baz')
->and($this->baz)
->toBe(1);
$this->baz = 2;
});
test('global beforeEach execution order', function () {
expect($this)
->toHaveProperty('baz')
->and($this->baz)
->toBe(2);
});

View File

@ -0,0 +1,10 @@
<?php
/*
* NOTE: To preserve cross-platform testing compatibility we cannot use ! * and
* other Windows reserved characters in this test's filename.
*
* See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
*/
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -0,0 +1,3 @@
<?php
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -0,0 +1,3 @@
<?php
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -0,0 +1,3 @@
<?php
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -0,0 +1,3 @@
<?php
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -0,0 +1,3 @@
<?php
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -0,0 +1,3 @@
<?php
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);

View File

@ -1,3 +1,20 @@
<?php <?php
uses()->group('integration')->in('Visual'); uses()->group('integration')->in('Visual');
$globalHook = (object) []; // NOTE: global test value container to be mutated and checked across files, as needed
uses()
->beforeEach(function () {
$this->baz = 0;
})
->beforeAll(function () use ($globalHook) {
$globalHook->beforeAll = 0;
})
->afterEach(function () {
$this->ith = 0;
})
->afterAll(function () use ($globalHook) {
$globalHook->afterAll = 0;
})
->in('Hooks');

View File

@ -18,7 +18,7 @@ test('default php unit tests', function () {
$testSuite->addTest($phpUnitTestCase); $testSuite->addTest($phpUnitTestCase);
expect($testSuite->tests())->toHaveCount(1); expect($testSuite->tests())->toHaveCount(1);
AddsTests::to($testSuite, new \Pest\TestSuite(getcwd())); AddsTests::to($testSuite, new \Pest\TestSuite(getcwd(), 'tests'));
expect($testSuite->tests())->toHaveCount(1); expect($testSuite->tests())->toHaveCount(1);
}); });
@ -27,6 +27,6 @@ it('removes warnings', function () {
$warningTestCase = new WarningTestCase('No tests found in class "Pest\TestCase".'); $warningTestCase = new WarningTestCase('No tests found in class "Pest\TestCase".');
$testSuite->addTest($warningTestCase); $testSuite->addTest($warningTestCase);
AddsTests::to($testSuite, new \Pest\TestSuite(getcwd())); AddsTests::to($testSuite, new \Pest\TestSuite(getcwd(), 'tests'));
expect($testSuite->tests())->toHaveCount(0); expect($testSuite->tests())->toHaveCount(0);
}); });

View File

@ -38,6 +38,7 @@ it('creates an instance and resolves also sub parameters', function () {
it('can resolve builtin value types', function () { it('can resolve builtin value types', function () {
$this->container->add('rootPath', getcwd()); $this->container->add('rootPath', getcwd());
$this->container->add('testPath', 'tests');
$instance = $this->container->get(TestSuite::class); $instance = $this->container->get(TestSuite::class);
expect($instance)->toBeInstanceOf(TestSuite::class); expect($instance)->toBeInstanceOf(TestSuite::class);

View File

@ -4,7 +4,7 @@ use Pest\Exceptions\TestAlreadyExist;
use Pest\TestSuite; use Pest\TestSuite;
it('does not allow to add the same test description twice', function () { it('does not allow to add the same test description twice', function () {
$testSuite = new TestSuite(getcwd()); $testSuite = new TestSuite(getcwd(), 'tests');
$test = function () {}; $test = function () {};
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test)); $testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
$this->expectException(TestAlreadyExist::class); $this->expectException(TestAlreadyExist::class);

View File

@ -13,7 +13,13 @@ test('visual snapshot of test suite on success', function () {
$process->run(); $process->run();
return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput()); return preg_replace([
'#\\x1b[[][^A-Za-z]*[A-Za-z]#',
'/(Tests\\\PHPUnit\\\CustomAffixes\\\InvalidTestName)([A-Za-z0-9]*)/',
], [
'',
'$1',
], $process->getOutput());
}; };
if (getenv('REBUILD_SNAPSHOTS')) { if (getenv('REBUILD_SNAPSHOTS')) {