mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Compare commits
346 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f6a882327 | |||
| fa7f930d50 | |||
| 84bc028796 | |||
| 7f0e83671b | |||
| 5b9198dad9 | |||
| a1a0f049cd | |||
| a7e231f64f | |||
| 1856c15e67 | |||
| ee4ff5a909 | |||
| 7de10d52a7 | |||
| 660df6d8d1 | |||
| d8666c8abf | |||
| b8ac4968ee | |||
| 78b3c41825 | |||
| b58a020423 | |||
| e337a52cda | |||
| 0a6873d8a6 | |||
| 09b7ab9a42 | |||
| 339414e348 | |||
| bb56d32643 | |||
| acd6bb2234 | |||
| cf93e316ff | |||
| 47cc88a6ab | |||
| 45bf9e8650 | |||
| 3c766c15d5 | |||
| aa407f34d5 | |||
| 077b5ffce0 | |||
| af6240b4ee | |||
| 78b328e970 | |||
| c92fa45ad7 | |||
| d050ef8d54 | |||
| 1c2f1facac | |||
| 8e695d62e9 | |||
| b9a301a13e | |||
| 4b9da9e3c1 | |||
| 239fceab9e | |||
| bedf033655 | |||
| 66f69617f1 | |||
| 5721e8c29a | |||
| 546d19fd84 | |||
| c1b32b9ffb | |||
| 298b1e6784 | |||
| 63f009fadf | |||
| 1066c2270d | |||
| 984f237a92 | |||
| dccd8239dd | |||
| e103623ecb | |||
| 542fc046d2 | |||
| 4720e0655b | |||
| a533772fe2 | |||
| 2218a0c137 | |||
| 2831629f1b | |||
| 1b7f1dc5b3 | |||
| 92b8d32ef7 | |||
| 2969c7a5e3 | |||
| 63c1faa9f4 | |||
| b5b14ef280 | |||
| 11eb1903c2 | |||
| a110848f9b | |||
| da5c21de8f | |||
| 408ae4cad8 | |||
| fc2484a28a | |||
| de46ee0f64 | |||
| 1e011c7b40 | |||
| 04dcebf3aa | |||
| e853792a59 | |||
| b0fbe54181 | |||
| 2a649bdfc0 | |||
| e042bf7d3a | |||
| 3ff71a4563 | |||
| 205238fcbf | |||
| ba06c5a76d | |||
| 78ffc491e9 | |||
| 7f38de11b7 | |||
| a6e34d204c | |||
| 66d47e4922 | |||
| 7d70b6e95a | |||
| 076dcab4c5 | |||
| 6f42e336c9 | |||
| 0d72b5197c | |||
| 7691e3c602 | |||
| b43a59868d | |||
| 457972716f | |||
| ae029660e3 | |||
| dc12419078 | |||
| f0ddd10a54 | |||
| 4daf7ee4ab | |||
| d60f320382 | |||
| 3c3e6b160b | |||
| c99f8f196e | |||
| 9cc4ecd5ab | |||
| 8d96f975e0 | |||
| 7f214f9e12 | |||
| da258fa89f | |||
| f23f857903 | |||
| fec11928cf | |||
| f6131d042b | |||
| 543b9542ae | |||
| 1681c1f4f8 | |||
| f41c3ce9ba | |||
| 847b06e558 | |||
| 601c4b01fc | |||
| 05c1c82ae2 | |||
| 1bde49b3c4 | |||
| b22f5e0c85 | |||
| dd643faa5c | |||
| facbf05016 | |||
| 58cff003d8 | |||
| ae997e6eee | |||
| e6c7d68def | |||
| 0b19672963 | |||
| a16a19e121 | |||
| 3dd10b3c7c | |||
| 12e63c7376 | |||
| 2f0cd7a4e3 | |||
| 447af55e7c | |||
| 253e9d10c8 | |||
| 536ce1eca0 | |||
| 4331b2aaf6 | |||
| 8d99cacc95 | |||
| 3c38facc8a | |||
| ed389d35d0 | |||
| 60c0636523 | |||
| 16b6f96b47 | |||
| 042f2ec3f3 | |||
| 851ce36010 | |||
| 4f386894bd | |||
| 2289adade2 | |||
| 29e21e3814 | |||
| 8367af22e7 | |||
| e3d678dc04 | |||
| 4ae482c707 | |||
| 075c31bc78 | |||
| 2125bf9668 | |||
| dbf3c0a8cf | |||
| eca5f89e59 | |||
| 0b0beac122 | |||
| 578e97123d | |||
| 01d672d563 | |||
| 490b2d66e5 | |||
| 0368c4846f | |||
| 4dfc02c5da | |||
| 5c84b0c6d3 | |||
| b6c06e8c30 | |||
| c6435d5606 | |||
| 2887d212e3 | |||
| cadae52d5d | |||
| d9749ca65b | |||
| c2070cd99d | |||
| 03d34e9a10 | |||
| 45e76a6df6 | |||
| 28dd3c2a03 | |||
| 5de981d923 | |||
| a55b31e7c3 | |||
| 5f0bd8180e | |||
| e1f1fcccbe | |||
| ab04aef561 | |||
| 79ddb1f58e | |||
| c7a2e68941 | |||
| 5c592928d4 | |||
| bcab4224fb | |||
| 892f70b5b5 | |||
| 5c7de5ad75 | |||
| 995088b522 | |||
| ef503646ee | |||
| a760470e48 | |||
| 1d4c1a5359 | |||
| 8e32b88fc8 | |||
| 1a7baad338 | |||
| 31d1b1b91d | |||
| 7524c80af6 | |||
| 721d5134b7 | |||
| 0b5321fdd7 | |||
| c86058fed1 | |||
| 8b295b5e9d | |||
| 221248e691 | |||
| 7621247bb7 | |||
| 463a50ebd4 | |||
| 62aabc6ae1 | |||
| 1ca9aa5ca6 | |||
| 7a76f8dce2 | |||
| beca27599c | |||
| 256b167eaf | |||
| 5526d4c24d | |||
| 7ea138c640 | |||
| c4a659c3b5 | |||
| 0a3991c314 | |||
| d1a9e0bbe3 | |||
| 17eacfdf95 | |||
| 9ec0762d41 | |||
| 30f39f1850 | |||
| 8ee07330b3 | |||
| cffde4564d | |||
| ce7a7649a2 | |||
| eeed7e6a0a | |||
| d6844f5239 | |||
| 06c4019e81 | |||
| 7785a8cc58 | |||
| 663516c1e3 | |||
| e83667a20b | |||
| aa96d75fb9 | |||
| a5af4bc5ed | |||
| 57161ba5ad | |||
| b1a9254fc1 | |||
| e56e818659 | |||
| 79de0d5875 | |||
| f6f8140ebc | |||
| eed221af46 | |||
| 524457a4e6 | |||
| 8e289b7a7d | |||
| 172b69cf15 | |||
| 475279a4fa | |||
| 4b236bf9ff | |||
| 50e2d10029 | |||
| fa8a57f1ab | |||
| 715f8b420b | |||
| c776bcf86d | |||
| a0637d86ff | |||
| 1a941d7f92 | |||
| b5959aa3fa | |||
| 8861dd2401 | |||
| da73f4b395 | |||
| d5097d0fe5 | |||
| 022ad4be0d | |||
| 2d2a83e9e8 | |||
| 67c7bee4fa | |||
| 675b0f1ec8 | |||
| c2b86c3ab3 | |||
| 46337b8085 | |||
| df172d8eed | |||
| 24b9160b79 | |||
| a7860b0b8e | |||
| 7471c224fa | |||
| 2996135155 | |||
| 1abab8d440 | |||
| e52c83e5be | |||
| 5ed4545737 | |||
| 8b39df68ce | |||
| 04d8a3762b | |||
| 0f7c8d00d6 | |||
| 12fb4f8639 | |||
| 6a84b825e6 | |||
| e8595c56b3 | |||
| 4a9fb2fa74 | |||
| d8fae6d689 | |||
| 252f9a0e46 | |||
| 8d24b4a217 | |||
| 43920f79a9 | |||
| 55bfc5856b | |||
| cd9d4acbc2 | |||
| 2b5355419a | |||
| 22b822ce87 | |||
| b2c298b926 | |||
| 671f3df115 | |||
| 2dd77001b7 | |||
| 5f574ded81 | |||
| 6309e6818d | |||
| 4813ab6ffb | |||
| 4ebba1298a | |||
| 2e0d1bb5a0 | |||
| 09d2b16767 | |||
| f56556eb73 | |||
| f387ca8624 | |||
| ca9d783cf9 | |||
| 863ddea50b | |||
| 00b4bb6305 | |||
| d217503a6a | |||
| b6012862c4 | |||
| 60c0ad006f | |||
| 79ff332afe | |||
| 595bbe32a4 | |||
| 328427bfdb | |||
| 51f556799c | |||
| 5e0a0855ea | |||
| 7ec3460d73 | |||
| 4c8c42cd20 | |||
| 09682dd393 | |||
| 82c18d3848 | |||
| 3bdba9210d | |||
| 371620d161 | |||
| d10281f851 | |||
| 47ceb2419b | |||
| 771b5b2e53 | |||
| 05f72f9b6d | |||
| f9de1b9c00 | |||
| 9516e56242 | |||
| 5f315fc899 | |||
| e59606818d | |||
| e16104350e | |||
| df31191f4e | |||
| 5b310f6f93 | |||
| 0200f90e9a | |||
| 027e69e48f | |||
| 33e01e3805 | |||
| 13781dcd14 | |||
| 164bad437a | |||
| 7ddcc03ad9 | |||
| bbe4445257 | |||
| d90ddf889c | |||
| 5907164749 | |||
| eb6de433b7 | |||
| 32f72cdf55 | |||
| c160d97428 | |||
| 2a8de0565f | |||
| 5ab3c6e2d7 | |||
| 50ece576a7 | |||
| 9c202fa2d7 | |||
| e4e9cb09e4 | |||
| 6a7597c01a | |||
| dd05452edd | |||
| 99ea9f42e5 | |||
| 7d6a86adc7 | |||
| 7fbd2661c8 | |||
| 6ce678d1c2 | |||
| 5049b996db | |||
| d838456caa | |||
| e45c4ff4f1 | |||
| fa3959db17 | |||
| b97e206f7a | |||
| 7e9edecc7f | |||
| dc75b34deb | |||
| 8e22803797 | |||
| c290909eb3 | |||
| f2e56da2da | |||
| e6b258534a | |||
| acef002a2d | |||
| 11ebe014fb | |||
| 2d13c6e219 | |||
| fbcb492c79 | |||
| 4f67eff619 | |||
| 3c2c767e09 | |||
| ff527baa1d | |||
| 621718d4b1 | |||
| 59adc57344 | |||
| 82bd836ae9 | |||
| e64856c664 | |||
| de86598c0d | |||
| 553b45306f | |||
| 2e7192ab95 | |||
| e21e3080e0 | |||
| 6a7ee90ff5 | |||
| 9d66893d5a | |||
| 64e780cf72 | |||
| 9bf141f698 | |||
| 9904094590 | |||
| 9e5b779abc |
4
.github/workflows/changelog.yml
vendored
4
.github/workflows/changelog.yml
vendored
@ -2,12 +2,12 @@ name: Changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ 1.x ]
|
||||
paths:
|
||||
- CHANGELOG.md
|
||||
- .github/workflows/changelog.yml
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ 1.x ]
|
||||
paths:
|
||||
- CHANGELOG.md
|
||||
jobs:
|
||||
|
||||
25
.github/workflows/tests.yml
vendored
25
.github/workflows/tests.yml
vendored
@ -8,10 +8,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
php: ['7.3', '7.4', '8.0']
|
||||
php: ['7.3', '7.4', '8.0', '8.1', '8.2']
|
||||
dependency-version: [prefer-lowest, prefer-stable]
|
||||
parallel: ['', '--parallel']
|
||||
exclude:
|
||||
- php: 8.1
|
||||
os: macos-latest
|
||||
- php: 8.1
|
||||
os: windows-latest
|
||||
- php: 8.2
|
||||
os: macos-latest
|
||||
- php: 8.2
|
||||
os: windows-latest
|
||||
|
||||
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
|
||||
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} - ${{ matrix.parallel }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -24,21 +34,16 @@ jobs:
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Setup Problem Matches
|
||||
- name: Setup Problem Matchers
|
||||
run: |
|
||||
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
||||
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
- name: Install PHP 7 dependencies
|
||||
- name: Install PHP dependencies
|
||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress
|
||||
if: "matrix.php < 8"
|
||||
|
||||
- name: Install PHP 8 dependencies
|
||||
run: composer update --${{ matrix.dependency-version }} --ignore-platform-req=php --no-interaction --no-progress
|
||||
if: "matrix.php >= 8"
|
||||
|
||||
- name: Unit Tests
|
||||
run: php bin/pest --colors=always --exclude-group=integration
|
||||
run: php bin/pest --colors=always --exclude-group=integration ${{ matrix.parallel }}
|
||||
|
||||
- name: Integration Tests
|
||||
run: php bin/pest --colors=always --group=integration
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'tests')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'bin')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'scripts')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'src')
|
||||
->append(['.php-cs-fixer.dist.php']);
|
||||
@ -21,6 +20,11 @@ $rules = [
|
||||
],
|
||||
'concat_space' => ['spacing' => 'one'],
|
||||
'not_operator_with_space' => false,
|
||||
'global_namespace_import' => [
|
||||
'import_classes' => true,
|
||||
'import_functions' => true,
|
||||
'import_constants' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$rules['increment_style'] = ['style' => 'post'];
|
||||
|
||||
128
CHANGELOG.md
128
CHANGELOG.md
@ -4,6 +4,134 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [v1.22.6 (2023-03-17)](https://github.com/pestphp/pest/compare/v1.22.5...v1.22.6)
|
||||
### Changed
|
||||
- Bumps dependencies
|
||||
|
||||
## [v1.22.5 (2023-03-03)](https://github.com/pestphp/pest/compare/v1.22.4...v1.22.5)
|
||||
### Changed
|
||||
- Bumps dependencies
|
||||
|
||||
## [v1.22.4 (2023-02-03)](https://github.com/pestphp/pest/compare/v1.22.3...v1.22.4)
|
||||
### Changed
|
||||
- Bumps dependencies
|
||||
|
||||
## [v1.22.3 (2022-12-07)](https://github.com/pestphp/pest/compare/v1.22.2...v1.22.3)
|
||||
### Fixed
|
||||
- Fixes ignoring datasets description on lazy datasets ([#612](https://github.com/pestphp/pest/pull/612))
|
||||
|
||||
## [v1.22.2 (2022-11-09)](https://github.com/pestphp/pest/compare/v1.22.1...v1.22.2)
|
||||
### Fixed
|
||||
- Fixes storing lazy datasets into internal array ([#602](https://github.com/pestphp/pest/pull/602))
|
||||
|
||||
## [v1.22.1 (2022-08-29)](https://github.com/pestphp/pest/compare/v1.22.0...v1.22.1)
|
||||
### Fixed
|
||||
- Initial PHP 8.2 support ([d050ef8](https://github.com/pestphp/pest/commit/d050ef8d54ffe8dfe015adeb1188aae2cca33c33))
|
||||
|
||||
## [v1.22.0 (2022-08-28)](https://github.com/pestphp/pest/compare/v1.21.3...v1.22.0)
|
||||
### Added
|
||||
- Initial PHP 8.2 support ([#563](https://github.com/pestphp/pest/pull/563))
|
||||
|
||||
## [v1.21.3 (2022-05-12)](https://github.com/pestphp/pest/compare/v1.21.2...v1.21.3)
|
||||
### Fixed
|
||||
- Debug of high order tests ([c1b32b9](https://github.com/pestphp/pest/commit/c1b32b9ffb5134803c490592454b11b8c05ea27d))
|
||||
|
||||
## [v1.21.2 (2022-03-05)](https://github.com/pestphp/pest/compare/v1.21.1...v1.21.2)
|
||||
### Fixed
|
||||
- `toThrow` expectation when exception does not exist ([#487](https://github.com/pestphp/pest/pull/487))
|
||||
|
||||
## [v1.21.1 (2021-11-25)](https://github.com/pestphp/pest/compare/v1.21.0...v1.21.1)
|
||||
### Fixed
|
||||
- sequence callables causing problems ([#442](https://github.com/pestphp/pest/pull/442))
|
||||
|
||||
## [v1.21.0 (2021-11-17)](https://github.com/pestphp/pest/compare/v1.20.0...v1.21.0)
|
||||
### Added
|
||||
- warn about xdebug modes ([1e011c](https://github.com/pestphp/pest/commit/1e011c7b4074d08f5dabab1f927d45383c85d210))
|
||||
|
||||
## [v1.20.0 (2021-09-25)](https://github.com/pestphp/pest/compare/v1.19.0...v1.20.0)
|
||||
### Added
|
||||
- `throwsIf` test call ([#371](https://github.com/pestphp/pest/pull/371))
|
||||
- `--ci` CLI option to ignore development options like `->local()` ([#405](https://github.com/pestphp/pest/pull/405))
|
||||
- `when` conditional expectation ([#406](https://github.com/pestphp/pest/pull/406))
|
||||
- `unless` conditional expectation ([b43a598](https://github.com/pestphp/pest/commit/b43a59868d5b790a28cbb29c6110c9f068b0b812))
|
||||
- `match` conditional expectation ([#407](https://github.com/pestphp/pest/pull/407))
|
||||
|
||||
### Fixed
|
||||
- `sequence` with more expectations than iterable elements ([#399](https://github.com/pestphp/pest/pull/399))
|
||||
|
||||
## [v1.19.0 (2021-09-20)](https://github.com/pestphp/pest/compare/v1.18.0...v1.19.0)
|
||||
### Added
|
||||
- PHP 8.1 support ([e6c7d68](https://github.com/pestphp/pest/commit/e6c7d68defaec8efe01e71e15dd8d8c45b0cf60f))
|
||||
- `toHaveProperties` expectation ([#391](https://github.com/pestphp/pest/pull/391))
|
||||
|
||||
## [v1.18.0 (2021-08-30)](https://github.com/pestphp/pest/compare/v1.17.0...v1.18.0)
|
||||
### Added
|
||||
- `toHaveLength` expectation ([#386](https://github.com/pestphp/pest/pull/386))
|
||||
- `nunomaduro/collision:^6.0` support ([4ae482c](https://github.com/pestphp/pest/commit/4ae482c7073fb77782b8a4b5738ef1fcea0f82ab))
|
||||
|
||||
## [v1.17.0 (2021-08-26)](https://github.com/pestphp/pest/compare/v1.16.0...v1.17.0)
|
||||
### Added
|
||||
- `toThrow` expectation ([#361](https://github.com/pestphp/pest/pull/361))
|
||||
|
||||
## [v1.16.0 (2021-08-19)](https://github.com/pestphp/pest/compare/v1.15.0...v1.16.0)
|
||||
### Added
|
||||
- Support for new parallel options ([#369](https://github.com/pestphp/pest/pull/369))
|
||||
|
||||
## [v1.15.0 (2021-08-04)](https://github.com/pestphp/pest/compare/v1.14.0...v1.15.0)
|
||||
### Added
|
||||
- `toBeTruthy` and `toBeFalsy` ([#367](https://github.com/pestphp/pest/pull/367))
|
||||
|
||||
## [v1.14.0 (2021-08-03)](https://github.com/pestphp/pest/compare/v1.13.0...v1.14.0)
|
||||
### Added
|
||||
- A new bound closure that allows you to access the test case in Datasets ([#364](https://github.com/pestphp/pest/pull/364))
|
||||
|
||||
## [v1.13.0 (2021-08-02)](https://github.com/pestphp/pest/compare/v1.12.0...v1.13.0)
|
||||
### Added
|
||||
- A cleaner output when running the Pest runner in PhpStorm ([#350](https://github.com/pestphp/pest/pull/350))
|
||||
- `toBeIn` expectation ([#363](https://github.com/pestphp/pest/pull/363))
|
||||
|
||||
### Fixed
|
||||
- `skip` with false condition marking test as skipped ([22b822c](https://github.com/pestphp/pest/commit/22b822ce87a3d19d84960fa5c93eb286820b525d))
|
||||
|
||||
## [v1.12.0 (2021-07-26)](https://github.com/pestphp/pest/compare/v1.11.0...v1.12.0)
|
||||
### Added
|
||||
- `--force` option to override tests in `pest:test` artisan command ([#353](https://github.com/pestphp/pest/pull/353))
|
||||
- Support for PHPUnit `^9.3.7` ([ca9d783](https://github.com/pestphp/pest/commit/ca9d783cf942a2caabc85ff7a728c7f28350c67a))
|
||||
|
||||
### Fixed
|
||||
- `beforeAll` and `afterAll` behind called multiple times per test ([#357](https://github.com/pestphp/pest/pull/357))
|
||||
|
||||
## [v1.11.0 (2021-07-21)](https://github.com/pestphp/pest/compare/v1.10.0...v1.11.0)
|
||||
### Added
|
||||
- Support for interacting with datasets in higher order tests ([#352](https://github.com/pestphp/pest/pull/352))
|
||||
|
||||
### Changed
|
||||
- The unit test stub now uses the expectation API ([#348](https://github.com/pestphp/pest/pull/348))
|
||||
|
||||
### Fixed
|
||||
- PhpStorm will no longer show 0 assertions in the output ([#349](https://github.com/pestphp/pest/pull/349))
|
||||
|
||||
## [v1.10.0 (2021-07-12)](https://github.com/pestphp/pest/compare/v1.9.1...v1.10.0)
|
||||
### Added
|
||||
- The ability to use higher order expectations inside higher order tests ([#341](https://github.com/pestphp/pest/pull/341))
|
||||
|
||||
## [v1.9.1 (2021-07-11)](https://github.com/pestphp/pest/compare/v1.9.0...v1.9.1)
|
||||
### Fixed
|
||||
- Callable `expect` values in higher order tests failing if the value was an existing method name ([#334](https://github.com/pestphp/pest/pull/344))
|
||||
|
||||
## [v1.9.0 (2021-07-09)](https://github.com/pestphp/pest/compare/v1.8.0...v1.9.0)
|
||||
### Changed
|
||||
- You may now pass just an exception message when using the `throws` method ([#339](https://github.com/pestphp/pest/pull/339))
|
||||
|
||||
## [v1.8.0 (2021-07-08)](https://github.com/pestphp/pest/compare/v1.7.1...v1.8.0)
|
||||
### Added
|
||||
- A new `tap` and test case aware `expect` methods for higher order tests ([#331](https://github.com/pestphp/pest/pull/331))
|
||||
- Access to test case methods and properties when using `skip` ([#338](https://github.com/pestphp/pest/pull/338))
|
||||
|
||||
## [v1.7.1 (2021-06-24)](https://github.com/pestphp/pest/compare/v1.7.0...v1.7.1)
|
||||
### Fixed
|
||||
- The `and` method not being usable in Higher Order expectations ([#330](https://github.com/pestphp/pest/pull/330))
|
||||
|
||||
## [v1.7.0 (2021-06-19)](https://github.com/pestphp/pest/compare/v1.6.0...v1.7.0)
|
||||
### Added
|
||||
- Support for non-callable values in the sequence method, which will be passed as `toEqual` ([#323](https://github.com/pestphp/pest/pull/323))
|
||||
|
||||
@ -31,6 +31,10 @@ composer lint
|
||||
```
|
||||
## Tests
|
||||
|
||||
Update the snapshots:
|
||||
```bash
|
||||
composer update:snapshots
|
||||
```
|
||||
Run all tests:
|
||||
```bash
|
||||
composer test
|
||||
|
||||
16
README.md
16
README.md
@ -19,10 +19,18 @@
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Pest development. If you are interested in becoming a sponsor, please visit the Nuno Maduro's [Sponsors page](https://github.com/sponsors/nunomaduro).
|
||||
|
||||
### Platinum Sponsors
|
||||
|
||||
- **[Spatie](https://spatie.be)**
|
||||
- **[Worksome](https://www.worksome.com/)**
|
||||
|
||||
### Premium Sponsors
|
||||
|
||||
- **[Scout APM](https://scoutapm.com)**
|
||||
- **[Akaunting](https://akaunting.com)**
|
||||
- **[Meema](https://meema.io/)**
|
||||
- [Akaunting](https://akaunting.com)
|
||||
- [Auth0](https://auth0.com)
|
||||
- [Codecourse](https://codecourse.com/)
|
||||
- [Fathom Analytics](https://usefathom.com/)
|
||||
- [Meema](https://meema.io)
|
||||
- [Scout APM](https://scoutapm.com)
|
||||
|
||||
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 is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
|
||||
@ -2,14 +2,15 @@
|
||||
|
||||
When releasing a new version of Pest there are some checks and updates that need to be done:
|
||||
|
||||
- Clear your local repository with: `git add . && git reset --hard && git checkout master`
|
||||
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...master](https://github.com/pestphp/pest/compare/{latest_version}...master) and update the [changelog](CHANGELOG.md) file with the main changes for this release
|
||||
- Clear your local repository with: `git add . && git reset --hard && git checkout 1.x`
|
||||
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...1.x](https://github.com/pestphp/pest/compare/{latest_version}...1.x) and update the [changelog](CHANGELOG.md) file with the main changes for this release
|
||||
- Update the version number in [src/Pest.php](src/Pest.php)
|
||||
- Run the tests locally using: `composer test`
|
||||
- Commit the CHANGELOG and Pest file with the message: `git commit -m "docs: update changelog"`
|
||||
- Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"`
|
||||
- Push the changes to GitHub
|
||||
- Check that the CI is passing as expected: [github.com/pestphp/pest/actions](https://github.com/pestphp/pest/actions)
|
||||
- Tag and push the tag with `git tag vX.X.X && git push --tags`
|
||||
- Publish release here: [github.com/pestphp/pest/releases/new](https://github.com/pestphp/pest/releases/new).
|
||||
|
||||
### Plugins
|
||||
|
||||
|
||||
20
bin/pest
20
bin/pest
@ -3,9 +3,9 @@
|
||||
|
||||
use NunoMaduro\Collision\Provider;
|
||||
use Pest\Actions\ValidatesEnvironment;
|
||||
use Pest\Console\Command;
|
||||
use Pest\Support\Container;
|
||||
use Pest\TestSuite;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
@ -27,7 +27,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
(new Provider())->register();
|
||||
|
||||
// get $rootPath based on $autoloadPath
|
||||
// Get $rootPath based on $autoloadPath
|
||||
$rootPath = dirname($autoloadPath, 2);
|
||||
$argv = new ArgvInput();
|
||||
|
||||
@ -42,14 +42,22 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
ValidatesEnvironment::in($testSuite);
|
||||
|
||||
// lets remove any arguments that PHPUnit does not understand
|
||||
$args = $_SERVER['argv'];
|
||||
|
||||
// Let's remove any arguments that PHPUnit does not understand
|
||||
if ($argv->hasParameterOption('--test-directory')) {
|
||||
foreach ($_SERVER['argv'] as $key => $value) {
|
||||
foreach ($args as $key => $value) {
|
||||
if (strpos($value, '--test-directory') !== false) {
|
||||
unset($_SERVER['argv'][$key]);
|
||||
unset($args[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit($container->get(Command::class)->run($_SERVER['argv']));
|
||||
if (($runInParallel = $argv->hasParameterOption(['--parallel', '-p'])) && !class_exists(\Pest\Parallel\Command::class)) {
|
||||
$output->writeln("Parallel support requires the Pest Parallel plugin. Run <fg=yellow;options=bold>`composer require --dev pestphp/pest-plugin-parallel`</> first.");
|
||||
exit(Command::FAILURE);
|
||||
}
|
||||
|
||||
$command = $runInParallel ? \Pest\Parallel\Command::class : \Pest\Console\Command::class;
|
||||
exit($container->get($command)->run($args));
|
||||
})();
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.3 || ^8.0",
|
||||
"nunomaduro/collision": "^5.4.0",
|
||||
"pestphp/pest-plugin": "^1.0.0",
|
||||
"phpunit/phpunit": ">= 9.3.7 <= 9.5.5"
|
||||
"nunomaduro/collision": "^5.11.0|^6.4.0",
|
||||
"pestphp/pest-plugin": "^1.1.0",
|
||||
"phpunit/phpunit": "^9.6.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -40,43 +40,50 @@
|
||||
]
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/console": "^8.47.0",
|
||||
"illuminate/support": "^8.47.0",
|
||||
"laravel/dusk": "^6.15.0",
|
||||
"pestphp/pest-dev-tools": "dev-master"
|
||||
"illuminate/console": "^8.83.27",
|
||||
"illuminate/support": "^8.83.27",
|
||||
"laravel/dusk": "^6.25.2",
|
||||
"pestphp/pest-dev-tools": "^1.0.0",
|
||||
"pestphp/pest-plugin-parallel": "^1.2.1"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"preferred-install": "dist"
|
||||
"preferred-install": "dist",
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/pest"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "php-cs-fixer fix -v",
|
||||
"test:lint": "php-cs-fixer fix -v --dry-run",
|
||||
"test:types": "phpstan analyse --ansi --memory-limit=0",
|
||||
"lint": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix -v",
|
||||
"test:lint": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix -v --dry-run",
|
||||
"test:types": "phpstan analyse --ansi --memory-limit=-1",
|
||||
"test:unit": "php bin/pest --colors=always --exclude-group=integration",
|
||||
"test:parallel": "php bin/pest -p --colors=always --exclude-group=integration",
|
||||
"test:integration": "php bin/pest --colors=always --group=integration",
|
||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
|
||||
"test": [
|
||||
"@test:lint",
|
||||
"@test:types",
|
||||
"@test:unit",
|
||||
"@test:parallel",
|
||||
"@test:integration"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
"dev-1.x": "1.x-dev"
|
||||
},
|
||||
"pest": {
|
||||
"plugins": [
|
||||
"Pest\\Plugins\\Coverage",
|
||||
"Pest\\Plugins\\Init",
|
||||
"Pest\\Plugins\\Version"
|
||||
"Pest\\Plugins\\Version",
|
||||
"Pest\\Plugins\\Environment"
|
||||
]
|
||||
},
|
||||
"laravel": {
|
||||
|
||||
262
phpstan-baseline.neon
Normal file
262
phpstan-baseline.neon
Normal file
@ -0,0 +1,262 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Cannot access an offset on mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/AddsDefaults.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$out of class Pest\\\\Logging\\\\JUnit constructor expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/AddsDefaults.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$verbose of class NunoMaduro\\\\Collision\\\\Adapters\\\\Phpunit\\\\Printer constructor expects bool, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/AddsDefaults.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$verbose of class Pest\\\\Logging\\\\TeamCity constructor expects bool, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/AddsDefaults.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$colors of class NunoMaduro\\\\Collision\\\\Adapters\\\\Phpunit\\\\Printer constructor expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/AddsDefaults.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$colors of class Pest\\\\Logging\\\\TeamCity constructor expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/AddsDefaults.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of function file_exists expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/ValidatesConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of method PHPUnit\\\\TextUI\\\\XmlConfiguration\\\\Loader\\:\\:load\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Actions/ValidatesConfiguration.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$loader of class PHPUnit\\\\TextUI\\\\TestRunner constructor expects PHPUnit\\\\Runner\\\\TestSuiteLoader\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Console/Command.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$testSuite of static method Pest\\\\Actions\\\\AddsTests\\:\\:to\\(\\) expects iterable\\<PHPUnit\\\\Framework\\\\TestCase\\>&PHPUnit\\\\Framework\\\\TestSuite, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Console/Command.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$suffixes of method SebastianBergmann\\\\FileIterator\\\\Facade\\:\\:getFilesAsArray\\(\\) expects array\\|string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Console/Command.php
|
||||
|
||||
-
|
||||
message: "#^Method Pest\\\\Datasets\\:\\:getDataSetsCombinations\\(\\) has parameter \\$combinations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Datasets.php
|
||||
|
||||
-
|
||||
message: "#^Method Pest\\\\Datasets\\:\\:getDataSetsCombinations\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Datasets.php
|
||||
|
||||
-
|
||||
message: "#^Method Pest\\\\Datasets\\:\\:processDatasets\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Datasets.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 1
|
||||
path: src/Each.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$ of callable callable\\(Pest\\\\Expectation\\<TValue\\>\\)\\: mixed expects Pest\\\\Expectation\\<TValue\\>, Pest\\\\Expectation\\<mixed\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$directory of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertDirectoryExists\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$directory of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertDirectoryIsReadable\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$directory of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertDirectoryIsWritable\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$file of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFileIsReadable\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$file of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFileIsWritable\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$filename of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFileExists\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$json of function json_decode expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$needle of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$object_or_class of function property_exists expects object\\|string, mixed given\\.$#"
|
||||
count: 2
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertContains\\(\\) expects iterable, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertCount\\(\\) expects Countable\\|iterable, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertMatchesRegularExpression\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringEndsWith\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$string of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringStartsWith\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$value of method Pest\\\\Expectation\\<TValue\\>\\:\\:retrieve\\(\\) expects array\\<string, null\\>\\|object, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Trying to invoke mixed but it's not a callable\\.$#"
|
||||
count: 1
|
||||
path: src/Expectation.php
|
||||
|
||||
-
|
||||
message: "#^Method Pest\\\\Factories\\\\TestCaseFactory\\:\\:build\\(\\) should return array\\<int, PHPUnit\\\\Framework\\\\TestCase\\> but returns array\\<int, object\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Factories/TestCaseFactory.php
|
||||
|
||||
-
|
||||
message: "#^Function it\\(\\) should return Pest\\\\PendingObjects\\\\TestCall but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Functions.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$classAndTraits of class Pest\\\\PendingObjects\\\\UsesCall constructor expects array\\<int, string\\>, array\\<int\\|string, string\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Functions.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$value of method Pest\\\\HigherOrderExpectation\\:\\:retrieve\\(\\) expects array\\<string, null\\>\\|object, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/HigherOrderExpectation.php
|
||||
|
||||
-
|
||||
message: "#^Dead catch \\- ReflectionException is never thrown in the try block\\.$#"
|
||||
count: 1
|
||||
path: src/Logging/JUnit.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$value of method DOMElement\\:\\:setAttribute\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Logging/JUnit.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\$test of method Pest\\\\Logging\\\\JUnit\\:\\:startTest\\(\\) has invalid type Pest\\\\Concerns\\\\Testable\\.$#"
|
||||
count: 1
|
||||
path: src/Logging/JUnit.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\$test of method Pest\\\\Logging\\\\TeamCity\\:\\:endTest\\(\\) has invalid type Pest\\\\Concerns\\\\Testable\\.$#"
|
||||
count: 1
|
||||
path: src/Logging/TeamCity.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\$test of method Pest\\\\Logging\\\\TeamCity\\:\\:startTest\\(\\) has invalid type Pest\\\\Concerns\\\\Testable\\.$#"
|
||||
count: 1
|
||||
path: src/Logging/TeamCity.php
|
||||
|
||||
-
|
||||
message: "#^Dead catch \\- PHPUnit\\\\Framework\\\\ExpectationFailedException is never thrown in the try block\\.$#"
|
||||
count: 1
|
||||
path: src/OppositeExpectation.php
|
||||
|
||||
-
|
||||
message: "#^Property Pest\\\\PendingObjects\\\\UsesCall\\:\\:\\$groups \\(array\\<int, string\\>\\) does not accept array\\<int\\|string, string\\>\\.$#"
|
||||
count: 1
|
||||
path: src/PendingObjects/UsesCall.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to float\\.$#"
|
||||
count: 1
|
||||
path: src/Plugins/Coverage.php
|
||||
|
||||
-
|
||||
message: "#^Static property Pest\\\\Plugins\\\\Environment\\:\\:\\$instance is unused\\.$#"
|
||||
count: 1
|
||||
path: src/Plugins/Environment.php
|
||||
|
||||
-
|
||||
message: "#^Method Pest\\\\Support\\\\Container\\:\\:get\\(\\) should return object but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Support/Container.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 1
|
||||
path: src/Support/ExceptionTrace.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$array of function key_exists expects array, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Support/ExceptionTrace.php
|
||||
|
||||
-
|
||||
message: "#^Method Pest\\\\Support\\\\HigherOrderCallables\\:\\:expect\\(\\) should return Pest\\\\Expectation\\<TValue\\> but returns Pest\\\\Expectation\\<mixed\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Support/HigherOrderCallables.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$target of method Pest\\\\Support\\\\HigherOrderMessage\\:\\:call\\(\\) expects object, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Support/HigherOrderMessageCollection.php
|
||||
|
||||
-
|
||||
message: "#^Constant Pest\\\\Support\\\\HigherOrderTapProxy\\:\\:UNDEFINED_PROPERTY is unused\\.$#"
|
||||
count: 1
|
||||
path: src/Support/HigherOrderTapProxy.php
|
||||
|
||||
-
|
||||
message: "#^Dead catch \\- Throwable is never thrown in the try block\\.$#"
|
||||
count: 1
|
||||
path: src/Support/HigherOrderTapProxy.php
|
||||
|
||||
@ -2,12 +2,12 @@ includes:
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
- vendor/ergebnis/phpstan-rules/rules.neon
|
||||
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: max
|
||||
paths:
|
||||
- src
|
||||
- scripts
|
||||
|
||||
checkMissingIterableValueType: true
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
@ -24,9 +24,6 @@ parameters:
|
||||
-
|
||||
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
|
||||
path: src/Logging
|
||||
-
|
||||
message: '#invalid typehint type Pest\\Concerns\\Testable#'
|
||||
path: src/Logging
|
||||
-
|
||||
message: '#is not subtype of native type PHPUnit\\Framework\\Test#'
|
||||
path: src/Logging
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$globalsFilePath = implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__),
|
||||
'vendor',
|
||||
'phpunit',
|
||||
'phpunit',
|
||||
'src',
|
||||
'Framework',
|
||||
'Assert',
|
||||
'Functions.php',
|
||||
]);
|
||||
|
||||
$compiledFilePath = implode(DIRECTORY_SEPARATOR, [dirname(__DIR__), 'compiled', 'globals.php']);
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
@unlink($compiledFilePath);
|
||||
|
||||
$replace = function ($contents, $string, $by) {
|
||||
return str_replace($string, $by, $contents);
|
||||
};
|
||||
|
||||
$remove = function ($contents, $string) {
|
||||
return str_replace($string, '', $contents);
|
||||
};
|
||||
|
||||
$contents = file_get_contents($globalsFilePath);
|
||||
$contents = $replace($contents, 'namespace PHPUnit\Framework;', 'use PHPUnit\Framework\Assert;');
|
||||
$contents = $remove($contents, 'use ArrayAccess;');
|
||||
$contents = $remove($contents, 'use Countable;');
|
||||
$contents = $remove($contents, 'use DOMDocument;');
|
||||
$contents = $remove($contents, 'use DOMElement;');
|
||||
$contents = $remove($contents, 'use Throwable;');
|
||||
|
||||
file_put_contents(implode(DIRECTORY_SEPARATOR, [dirname(__DIR__), 'compiled', 'globals.php']), $contents);
|
||||
@ -30,7 +30,7 @@ final class AddsDefaults
|
||||
}
|
||||
|
||||
if ($arguments[self::PRINTER] === \PHPUnit\Util\Log\TeamCity::class) {
|
||||
$arguments[self::PRINTER] = new TeamCity($arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
|
||||
$arguments[self::PRINTER] = new TeamCity(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
|
||||
}
|
||||
|
||||
// Load our junit logger instead.
|
||||
|
||||
@ -22,8 +22,6 @@ final class AddsTests
|
||||
{
|
||||
self::removeTestClosureWarnings($testSuite);
|
||||
|
||||
// @todo refactor this...
|
||||
|
||||
$testSuites = [];
|
||||
$pestTestSuite->tests->build($pestTestSuite, function (TestCase $testCase) use (&$testSuites): void {
|
||||
$testCaseClass = get_class($testCase);
|
||||
|
||||
50
src/Actions/InteractsWithPlugins.php
Normal file
50
src/Actions/InteractsWithPlugins.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Contracts\Plugins\AddsOutput;
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
use Pest\Plugin\Loader;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InteractsWithPlugins
|
||||
{
|
||||
/**
|
||||
* Transform the input arguments by passing it to the relevant plugins.
|
||||
*
|
||||
* @param array<int, string> $argv
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function handleArguments(array $argv): array
|
||||
{
|
||||
$plugins = Loader::getPlugins(HandlesArguments::class);
|
||||
|
||||
/** @var HandlesArguments $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$argv = $plugin->handleArguments($argv);
|
||||
}
|
||||
|
||||
return $argv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an opportunity for any plugins that want
|
||||
* to provide additional output after test execution.
|
||||
*/
|
||||
public static function addOutput(int $result): int
|
||||
{
|
||||
$plugins = Loader::getPlugins(AddsOutput::class);
|
||||
|
||||
/** @var AddsOutput $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$result = $plugin->addOutput($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@ -5,11 +5,12 @@ declare(strict_types=1);
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Support\Str;
|
||||
use function Pest\testDirectory;
|
||||
use PHPUnit\Util\FileLoader;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
use function Pest\testDirectory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -50,6 +51,7 @@ final class LoadStructure
|
||||
$directory = new RecursiveDirectoryIterator($filename);
|
||||
$iterator = new RecursiveIteratorIterator($directory);
|
||||
foreach ($iterator as $file) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$filename = $file->__toString();
|
||||
if (Str::endsWith($filename, '.php') && file_exists($filename)) {
|
||||
require_once $filename;
|
||||
|
||||
33
src/Concerns/Logging/WritesToConsole.php
Normal file
33
src/Concerns/Logging/WritesToConsole.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Concerns\Logging;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait WritesToConsole
|
||||
{
|
||||
private function writeSuccess(string $message): void
|
||||
{
|
||||
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
|
||||
}
|
||||
|
||||
private function writeError(string $message): void
|
||||
{
|
||||
$this->writePestTestOutput($message, 'fg-red, bold', '⨯');
|
||||
}
|
||||
|
||||
private function writeWarning(string $message): void
|
||||
{
|
||||
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
|
||||
}
|
||||
|
||||
private function writePestTestOutput(string $message, string $color, string $symbol): void
|
||||
{
|
||||
$this->writeWithColor($color, "$symbol ", false);
|
||||
$this->write($message);
|
||||
$this->writeNewLine();
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,8 @@ use Pest\TestSuite;
|
||||
use PHPUnit\Framework\ExecutionOrderDependency;
|
||||
use Throwable;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* To avoid inheritance conflicts, all the fields related
|
||||
* to Pest only will be prefixed by double underscore.
|
||||
@ -73,6 +75,8 @@ trait Testable
|
||||
{
|
||||
$this->__test = $test;
|
||||
$this->__description = $description;
|
||||
self::$beforeAll = null;
|
||||
self::$afterAll = null;
|
||||
|
||||
parent::__construct('__test', $data);
|
||||
}
|
||||
@ -255,7 +259,7 @@ trait Testable
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return \sprintf(
|
||||
return sprintf(
|
||||
'%s::%s',
|
||||
self::$__filename,
|
||||
$this->__description
|
||||
@ -271,7 +275,19 @@ trait Testable
|
||||
*/
|
||||
public function __test()
|
||||
{
|
||||
return $this->__callClosure($this->__test, func_get_args());
|
||||
return $this->__callClosure($this->__test, $this->resolveTestArguments(func_get_args()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the passed arguments. Any Closures will be bound to the testcase and resolved.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function resolveTestArguments(array $arguments): array
|
||||
{
|
||||
return array_map(function ($data) {
|
||||
return $data instanceof Closure ? $this->__callClosure($data, []) : $data;
|
||||
}, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -6,11 +6,9 @@ namespace Pest\Console;
|
||||
|
||||
use Pest\Actions\AddsDefaults;
|
||||
use Pest\Actions\AddsTests;
|
||||
use Pest\Actions\InteractsWithPlugins;
|
||||
use Pest\Actions\LoadStructure;
|
||||
use Pest\Actions\ValidatesConfiguration;
|
||||
use Pest\Contracts\Plugins\AddsOutput;
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
use Pest\Plugin\Loader;
|
||||
use Pest\Plugins\Version;
|
||||
use Pest\Support\Container;
|
||||
use Pest\TestSuite;
|
||||
@ -20,6 +18,8 @@ use PHPUnit\TextUI\TestRunner;
|
||||
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function is_dir;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -57,23 +57,12 @@ final class Command extends BaseCommand
|
||||
*/
|
||||
protected function handleArguments(array $argv): void
|
||||
{
|
||||
/*
|
||||
* First, let's call all plugins that want to handle arguments
|
||||
*/
|
||||
$plugins = Loader::getPlugins(HandlesArguments::class);
|
||||
$argv = InteractsWithPlugins::handleArguments($argv);
|
||||
|
||||
/** @var HandlesArguments $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$argv = $plugin->handleArguments($argv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Next, as usual, let's send the console arguments to PHPUnit.
|
||||
*/
|
||||
parent::handleArguments($argv);
|
||||
|
||||
/*
|
||||
* Finally, let's validate the configuration. Making
|
||||
* Let's validate the configuration. Making
|
||||
* sure all options are yet supported by Pest.
|
||||
*/
|
||||
ValidatesConfiguration::in($this->arguments);
|
||||
@ -94,7 +83,7 @@ final class Command extends BaseCommand
|
||||
$testSuite = $this->arguments['test'];
|
||||
|
||||
if (is_string($testSuite)) {
|
||||
if (\is_dir($testSuite)) {
|
||||
if (is_dir($testSuite)) {
|
||||
/** @var string[] $files */
|
||||
$files = (new FileIteratorFacade())->getFilesAsArray(
|
||||
$testSuite,
|
||||
@ -128,16 +117,7 @@ final class Command extends BaseCommand
|
||||
LoadStructure::in($this->testSuite->rootPath);
|
||||
|
||||
$result = parent::run($argv, false);
|
||||
|
||||
/*
|
||||
* Let's call all plugins that want to add output after test execution
|
||||
*/
|
||||
$plugins = Loader::getPlugins(AddsOutput::class);
|
||||
|
||||
/** @var AddsOutput $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$result = $plugin->addOutput($result);
|
||||
}
|
||||
$result = InteractsWithPlugins::addOutput($result);
|
||||
|
||||
exit($result);
|
||||
}
|
||||
|
||||
@ -12,9 +12,6 @@ interface HandlesArguments
|
||||
/**
|
||||
* Allows to handle custom command line arguments.
|
||||
*
|
||||
* PLEASE NOTE: it is necessary to remove any custom argument from the array
|
||||
* because otherwise the application will complain about them
|
||||
*
|
||||
* @param array<int, string> $arguments
|
||||
*
|
||||
* @return array<int, string> the updated list of arguments
|
||||
|
||||
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Pest;
|
||||
|
||||
use Closure;
|
||||
use Generator;
|
||||
use Pest\Exceptions\DatasetAlreadyExist;
|
||||
use Pest\Exceptions\DatasetDoesNotExist;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
@ -122,7 +123,10 @@ final class Datasets
|
||||
}
|
||||
|
||||
if ($datasets[$index] instanceof Traversable) {
|
||||
$datasets[$index] = iterator_to_array($datasets[$index]);
|
||||
$preserveKeysForArrayIterator = $datasets[$index] instanceof Generator
|
||||
&& is_string($datasets[$index]->key());
|
||||
|
||||
$datasets[$index] = iterator_to_array($datasets[$index], $preserveKeysForArrayIterator);
|
||||
}
|
||||
|
||||
foreach ($datasets[$index] as $key => $values) {
|
||||
|
||||
36
src/Exceptions/DatasetMissing.php
Normal file
36
src/Exceptions/DatasetMissing.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use BadFunctionCallException;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
|
||||
/**
|
||||
* Creates a new instance of dataset is not present for test that has arguments.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Create new exception instance.
|
||||
*
|
||||
* @param array<string, string> $args A map of argument names to their typee
|
||||
*/
|
||||
public function __construct(string $file, string $name, array $args)
|
||||
{
|
||||
parent::__construct(sprintf(
|
||||
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
|
||||
$name,
|
||||
count($args),
|
||||
implode(', ', array_map(static function (string $arg, string $type): string {
|
||||
return sprintf('%s $%s', $type, $arg);
|
||||
}, array_keys($args), $args)),
|
||||
$file,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,20 @@ declare(strict_types=1);
|
||||
namespace Pest;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Closure;
|
||||
use Error;
|
||||
use InvalidArgumentException;
|
||||
use Pest\Concerns\Extendable;
|
||||
use Pest\Concerns\RetrievesValues;
|
||||
use Pest\Support\Arr;
|
||||
use Pest\Support\NullClosure;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionNamedType;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -102,7 +109,6 @@ final class Expectation
|
||||
public function ray(...$arguments): self
|
||||
{
|
||||
if (function_exists('ray')) {
|
||||
// @phpstan-ignore-next-line
|
||||
ray($this->value, ...$arguments);
|
||||
}
|
||||
|
||||
@ -151,6 +157,7 @@ final class Expectation
|
||||
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
|
||||
$keys = array_keys($value);
|
||||
$values = array_values($value);
|
||||
$callbacksCount = count($callbacks);
|
||||
|
||||
$index = 0;
|
||||
|
||||
@ -159,8 +166,12 @@ final class Expectation
|
||||
$index = $index < count($values) - 1 ? $index + 1 : 0;
|
||||
}
|
||||
|
||||
if ($callbacksCount > count($values)) {
|
||||
Assert::assertLessThanOrEqual(count($value), count($callbacks));
|
||||
}
|
||||
|
||||
foreach ($values as $key => $item) {
|
||||
if (is_callable($callbacks[$key])) {
|
||||
if ($callbacks[$key] instanceof Closure) {
|
||||
call_user_func($callbacks[$key], new self($item), new self($keys[$key]));
|
||||
continue;
|
||||
}
|
||||
@ -171,6 +182,88 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the subject matches one of the given "expressions", the expression callback will run.
|
||||
*
|
||||
* @template TMatchSubject of array-key
|
||||
*
|
||||
* @param callable(): TMatchSubject|TMatchSubject $subject
|
||||
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
|
||||
*/
|
||||
public function match($subject, array $expressions): Expectation
|
||||
{
|
||||
$subject = is_callable($subject)
|
||||
? $subject
|
||||
: function () use ($subject) {
|
||||
return $subject;
|
||||
};
|
||||
|
||||
$subject = $subject();
|
||||
|
||||
$matched = false;
|
||||
|
||||
foreach ($expressions as $key => $callback) {
|
||||
if ($subject != $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matched = true;
|
||||
|
||||
if (is_callable($callback)) {
|
||||
$callback(new self($this->value));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->and($this->value)->toEqual($callback);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($matched === false) {
|
||||
throw new ExpectationFailedException('Unhandled match value.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback if the given "condition" is falsy.
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
* @param callable(Expectation<TValue>): mixed $callback
|
||||
*/
|
||||
public function unless($condition, callable $callback): Expectation
|
||||
{
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: static function () use ($condition): bool {
|
||||
return (bool) $condition; // @phpstan-ignore-line
|
||||
};
|
||||
|
||||
return $this->when(!$condition(), $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the callback if the given "condition" is truthy.
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
* @param callable(Expectation<TValue>): mixed $callback
|
||||
*/
|
||||
public function when($condition, callable $callback): Expectation
|
||||
{
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: static function () use ($condition): bool {
|
||||
return (bool) $condition; // @phpstan-ignore-line
|
||||
};
|
||||
|
||||
if ($condition()) {
|
||||
$callback($this->and($this->value));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that two variables have the same type and
|
||||
* value. Used on objects, it asserts that two
|
||||
@ -205,6 +298,16 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value is truthy.
|
||||
*/
|
||||
public function toBeTruthy(): Expectation
|
||||
{
|
||||
Assert::assertTrue((bool) $this->value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value is false.
|
||||
*/
|
||||
@ -215,6 +318,16 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value is falsy.
|
||||
*/
|
||||
public function toBeFalsy(): Expectation
|
||||
{
|
||||
Assert::assertFalse((bool) $this->value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value is greater than $expected.
|
||||
*
|
||||
@ -266,15 +379,17 @@ final class Expectation
|
||||
/**
|
||||
* Asserts that $needle is an element of the value.
|
||||
*
|
||||
* @param mixed $needle
|
||||
* @param mixed $needles
|
||||
*/
|
||||
public function toContain($needle): Expectation
|
||||
public function toContain(...$needles): Expectation
|
||||
{
|
||||
foreach ($needles as $needle) {
|
||||
if (is_string($this->value)) {
|
||||
Assert::assertStringContainsString($needle, $this->value);
|
||||
} else {
|
||||
Assert::assertContains($needle, $this->value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -299,6 +414,36 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that $number matches value's Length.
|
||||
*/
|
||||
public function toHaveLength(int $number): Expectation
|
||||
{
|
||||
if (is_string($this->value)) {
|
||||
Assert::assertEquals($number, mb_strlen($this->value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_iterable($this->value)) {
|
||||
return $this->toHaveCount($number);
|
||||
}
|
||||
|
||||
if (is_object($this->value)) {
|
||||
if (method_exists($this->value, 'toArray')) {
|
||||
$array = $this->value->toArray();
|
||||
} else {
|
||||
$array = (array) $this->value;
|
||||
}
|
||||
|
||||
Assert::assertCount($number, $array);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new BadMethodCallException('Expectation value length is not countable.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that $count matches the number of elements of the value.
|
||||
*/
|
||||
@ -328,6 +473,20 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value contains the provided properties $names.
|
||||
*
|
||||
* @param iterable<array-key, string> $names
|
||||
*/
|
||||
public function toHaveProperties(iterable $names): Expectation
|
||||
{
|
||||
foreach ($names as $name) {
|
||||
$this->toHaveProperty($name);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that two variables have the same value.
|
||||
*
|
||||
@ -371,6 +530,18 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value is one of the given values.
|
||||
*
|
||||
* @param iterable<int|string, mixed> $values
|
||||
*/
|
||||
public function toBeIn(iterable $values): Expectation
|
||||
{
|
||||
Assert::assertContains($this->value, $values);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value is infinite.
|
||||
*/
|
||||
@ -715,6 +886,60 @@ final class Expectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that executing value throws an exception.
|
||||
*
|
||||
* @param (Closure(Throwable): mixed)|string $exception
|
||||
*/
|
||||
public function toThrow($exception, string $exceptionMessage = null): Expectation
|
||||
{
|
||||
$callback = NullClosure::create();
|
||||
|
||||
if ($exception instanceof Closure) {
|
||||
$callback = $exception;
|
||||
$parameters = (new ReflectionFunction($exception))->getParameters();
|
||||
|
||||
if (1 !== count($parameters)) {
|
||||
throw new InvalidArgumentException('The given closure must have a single parameter type-hinted as the class string.');
|
||||
}
|
||||
|
||||
if (!($type = $parameters[0]->getType()) instanceof ReflectionNamedType) {
|
||||
throw new InvalidArgumentException('The given closure\'s parameter must be type-hinted as the class string.');
|
||||
}
|
||||
|
||||
$exception = $type->getName();
|
||||
}
|
||||
|
||||
try {
|
||||
($this->value)();
|
||||
} catch (Throwable $e) {
|
||||
if (!class_exists($exception)) {
|
||||
if ($e instanceof Error && (bool) preg_match("/Class [\"']{$exception}[\"'] not found/", $e->getMessage())) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Assert::assertStringContainsString($exception, $e->getMessage());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($exceptionMessage !== null) {
|
||||
Assert::assertStringContainsString($exceptionMessage, $e->getMessage());
|
||||
}
|
||||
|
||||
Assert::assertInstanceOf($exception, $e);
|
||||
$callback($e);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!class_exists($exception)) {
|
||||
throw new ExpectationFailedException("Exception with message \"{$exception}\" not thrown.");
|
||||
}
|
||||
|
||||
throw new ExpectationFailedException("Exception \"{$exception}\" not thrown.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the given value.
|
||||
*
|
||||
|
||||
@ -216,6 +216,7 @@ final class TestCaseFactory
|
||||
eval("
|
||||
namespace $namespace;
|
||||
|
||||
#[\AllowDynamicProperties]
|
||||
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
|
||||
$traitsCode
|
||||
|
||||
@ -228,4 +229,13 @@ final class TestCaseFactory
|
||||
|
||||
return $classFQN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the test case will receive argument input from Pest, or not.
|
||||
*/
|
||||
public function receivesArguments(): bool
|
||||
{
|
||||
return count($this->datasets) > 0
|
||||
|| $this->factoryProxies->count('addDependencies') > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +63,20 @@ final class HigherOrderExpectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @template TValue
|
||||
*
|
||||
* @param TValue $value
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function and($value): Expectation
|
||||
{
|
||||
return $this->expect($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class with the given arguments.
|
||||
*
|
||||
|
||||
@ -8,9 +8,10 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Pest\Exceptions\InvalidConsoleArgument;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
|
||||
use function Pest\testDirectory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@ -8,9 +8,10 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Pest\Console\Thanks;
|
||||
use Pest\Exceptions\InvalidConsoleArgument;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
|
||||
use function Pest\testDirectory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@ -8,9 +8,10 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Pest\Exceptions\InvalidConsoleArgument;
|
||||
use Pest\Support\Str;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
|
||||
use function Pest\testDirectory;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -21,7 +22,7 @@ final class PestTestCommand extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test} {--test-directory=tests : The name of the tests directory}';
|
||||
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test} {--test-directory=tests : The name of the tests directory} {--force : Overwrite the existing test file with the same name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -56,7 +57,7 @@ final class PestTestCommand extends Command
|
||||
File::makeDirectory(dirname($target), 0777, true, true);
|
||||
}
|
||||
|
||||
if (File::exists($target)) {
|
||||
if (File::exists($target) && !(bool) $this->option('force')) {
|
||||
throw new InvalidConsoleArgument(sprintf('%s already exist', $target));
|
||||
}
|
||||
|
||||
|
||||
@ -12,12 +12,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Logging;
|
||||
|
||||
use function class_exists;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use Exception;
|
||||
use function get_class;
|
||||
use function method_exists;
|
||||
use Pest\Concerns\Testable;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use PHPUnit\Framework\ExceptionWrapper;
|
||||
@ -32,9 +29,13 @@ use PHPUnit\Util\Printer;
|
||||
use PHPUnit\Util\Xml;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use Throwable;
|
||||
|
||||
use function class_exists;
|
||||
use function get_class;
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use Throwable;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
@ -173,7 +174,6 @@ final class JUnit extends Printer implements TestListener
|
||||
$this->doAddSkipped();
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function startTestSuite(TestSuite $suite): void
|
||||
{
|
||||
$testSuite = $this->document->createElement('testsuite');
|
||||
@ -212,7 +212,6 @@ final class JUnit extends Printer implements TestListener
|
||||
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function endTestSuite(TestSuite $suite): void
|
||||
{
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
@ -283,7 +282,6 @@ final class JUnit extends Printer implements TestListener
|
||||
$class = new ReflectionClass($test);
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (ReflectionException $e) {
|
||||
// @phpstan-ignore-next-line
|
||||
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
@ -4,26 +4,36 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Logging;
|
||||
|
||||
use function getmypid;
|
||||
use Pest\Concerns\Logging\WritesToConsole;
|
||||
use Pest\Concerns\Testable;
|
||||
use Pest\Support\ExceptionTrace;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use PHPUnit\Framework\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\TestResult;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Framework\Warning;
|
||||
use PHPUnit\TextUI\DefaultResultPrinter;
|
||||
use Throwable;
|
||||
|
||||
use function getmypid;
|
||||
use function Pest\version;
|
||||
use function round;
|
||||
use function str_replace;
|
||||
use Throwable;
|
||||
use function strlen;
|
||||
|
||||
final class TeamCity extends DefaultResultPrinter
|
||||
{
|
||||
use WritesToConsole;
|
||||
private const PROTOCOL = 'pest_qn://';
|
||||
private const NAME = 'name';
|
||||
private const LOCATION_HINT = 'locationHint';
|
||||
private const DURATION = 'duration';
|
||||
private const TEST_SUITE_STARTED = 'testSuiteStarted';
|
||||
private const TEST_SUITE_FINISHED = 'testSuiteFinished';
|
||||
private const TEST_COUNT = 'testCount';
|
||||
private const TEST_STARTED = 'testStarted';
|
||||
private const TEST_FINISHED = 'testFinished';
|
||||
|
||||
/** @var int */
|
||||
private $flowId;
|
||||
@ -34,140 +44,97 @@ final class TeamCity extends DefaultResultPrinter
|
||||
/** @var \PHPUnit\Util\Log\TeamCity */
|
||||
private $phpunitTeamCity;
|
||||
|
||||
public function __construct(bool $verbose, string $colors)
|
||||
/**
|
||||
* @param resource|string|null $out
|
||||
*/
|
||||
public function __construct($out, bool $verbose, string $colors)
|
||||
{
|
||||
parent::__construct(null, $verbose, $colors, false, 80, false);
|
||||
$this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity(
|
||||
null,
|
||||
$verbose,
|
||||
$colors,
|
||||
false,
|
||||
80,
|
||||
false
|
||||
);
|
||||
parent::__construct($out, $verbose, $colors);
|
||||
$this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity($out, $verbose, $colors);
|
||||
|
||||
$this->logo();
|
||||
}
|
||||
|
||||
private function logo(): void
|
||||
{
|
||||
$this->writeNewLine();
|
||||
$this->write('Pest ' . version());
|
||||
$this->writeNewLine();
|
||||
}
|
||||
|
||||
public function printResult(TestResult $result): void
|
||||
{
|
||||
$this->printHeader($result);
|
||||
$this->printFooter($result);
|
||||
$this->write('Tests: ');
|
||||
|
||||
$results = [
|
||||
'failed' => ['count' => $result->errorCount() + $result->failureCount(), 'color' => 'fg-red'],
|
||||
'skipped' => ['count' => $result->skippedCount(), 'color' => 'fg-yellow'],
|
||||
'warned' => ['count' => $result->warningCount(), 'color' => 'fg-yellow'],
|
||||
'risked' => ['count' => $result->riskyCount(), 'color' => 'fg-yellow'],
|
||||
'incomplete' => ['count' => $result->notImplementedCount(), 'color' => 'fg-yellow'],
|
||||
'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'],
|
||||
];
|
||||
|
||||
$filteredResults = array_filter($results, function ($item): bool {
|
||||
return $item['count'] > 0;
|
||||
});
|
||||
|
||||
foreach ($filteredResults as $key => $info) {
|
||||
$this->writeWithColor($info['color'], $info['count'] . " $key", false);
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
if ($key !== array_reverse(array_keys($filteredResults))[0]) {
|
||||
$this->write(', ');
|
||||
}
|
||||
}
|
||||
|
||||
$this->writeNewLine();
|
||||
$this->write("Assertions: $this->numAssertions");
|
||||
|
||||
$this->writeNewLine();
|
||||
$this->write("Time: {$result->time()}s");
|
||||
|
||||
$this->writeNewLine();
|
||||
}
|
||||
|
||||
private function successfulTestCount(TestResult $result): int
|
||||
{
|
||||
return $result->count()
|
||||
- $result->failureCount()
|
||||
- $result->errorCount()
|
||||
- $result->skippedCount()
|
||||
- $result->warningCount()
|
||||
- $result->notImplementedCount()
|
||||
- $result->riskyCount();
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function startTestSuite(TestSuite $suite): void
|
||||
{
|
||||
$suiteName = $suite->getName();
|
||||
|
||||
if (static::isCompoundTestSuite($suite)) {
|
||||
$this->writeWithColor('bold', ' ' . $suiteName);
|
||||
} elseif (static::isPestTestSuite($suite)) {
|
||||
$this->writeWithColor('fg-white, bold', ' ' . substr_replace($suiteName, '', 0, 2) . ' ');
|
||||
} else {
|
||||
$this->writeWithColor('fg-white, bold', ' ' . $suiteName);
|
||||
}
|
||||
|
||||
$this->writeNewLine();
|
||||
|
||||
$this->flowId = (int) getmypid();
|
||||
|
||||
if (!$this->isSummaryTestCountPrinted) {
|
||||
$this->printEvent(
|
||||
'testCount',
|
||||
['count' => $suite->count()]
|
||||
);
|
||||
$this->printEvent(self::TEST_COUNT, [
|
||||
'count' => $suite->count(),
|
||||
]);
|
||||
$this->isSummaryTestCountPrinted = true;
|
||||
}
|
||||
|
||||
$suiteName = $suite->getName();
|
||||
|
||||
if (file_exists($suiteName) || !method_exists($suiteName, '__getFileName')) {
|
||||
$this->printEvent(
|
||||
self::TEST_SUITE_STARTED, [
|
||||
self::NAME => $suiteName,
|
||||
self::LOCATION_HINT => self::PROTOCOL . $suiteName,
|
||||
$this->printEvent(self::TEST_SUITE_STARTED, [
|
||||
self::NAME => static::isCompoundTestSuite($suite) ? $suiteName : substr($suiteName, 2),
|
||||
self::LOCATION_HINT => self::PROTOCOL . (static::isCompoundTestSuite($suite) ? $suiteName : $suiteName::__getFileName()),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fileName = $suiteName::__getFileName();
|
||||
|
||||
$this->printEvent(
|
||||
self::TEST_SUITE_STARTED, [
|
||||
self::NAME => substr($suiteName, 2),
|
||||
self::LOCATION_HINT => self::PROTOCOL . $fileName,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function endTestSuite(TestSuite $suite): void
|
||||
{
|
||||
$suiteName = $suite->getName();
|
||||
|
||||
if (file_exists($suiteName) || !method_exists($suiteName, '__getFileName')) {
|
||||
$this->printEvent(
|
||||
self::TEST_SUITE_FINISHED, [
|
||||
self::NAME => $suiteName,
|
||||
self::LOCATION_HINT => self::PROTOCOL . $suiteName,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->printEvent(
|
||||
self::TEST_SUITE_FINISHED, [
|
||||
self::NAME => substr($suiteName, 2),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Test|Testable $test
|
||||
*/
|
||||
public function startTest(Test $test): void
|
||||
{
|
||||
if (!TeamCity::isPestTest($test)) {
|
||||
$this->phpunitTeamCity->startTest($test);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->printEvent('testStarted', [
|
||||
self::NAME => $test->getName(),
|
||||
// @phpstan-ignore-next-line
|
||||
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Test|Testable $test
|
||||
*/
|
||||
public function endTest(Test $test, float $time): void
|
||||
{
|
||||
if (!TeamCity::isPestTest($test)) {
|
||||
$this->phpunitTeamCity->endTest($test, $time);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->printEvent('testFinished', [
|
||||
self::NAME => $test->getName(),
|
||||
self::DURATION => self::toMilliseconds($time),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Test|Testable $test
|
||||
*/
|
||||
public function addError(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->phpunitTeamCity->addError($test, $t, $time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-ignore-next-line
|
||||
*
|
||||
* @param Test|Testable $test
|
||||
*/
|
||||
public function addWarning(Test $test, Warning $e, float $time): void
|
||||
{
|
||||
$this->phpunitTeamCity->addWarning($test, $e, $time);
|
||||
}
|
||||
|
||||
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
|
||||
{
|
||||
$this->phpunitTeamCity->addFailure($test, $e, $time);
|
||||
}
|
||||
|
||||
protected function writeProgress(string $progress): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,7 +142,7 @@ final class TeamCity extends DefaultResultPrinter
|
||||
*/
|
||||
private function printEvent(string $eventName, array $params = []): void
|
||||
{
|
||||
$this->write("\n##teamcity[{$eventName}");
|
||||
$this->write("##teamcity[{$eventName}");
|
||||
|
||||
if ($this->flowId !== 0) {
|
||||
$params['flowId'] = $this->flowId;
|
||||
@ -198,9 +165,55 @@ final class TeamCity extends DefaultResultPrinter
|
||||
);
|
||||
}
|
||||
|
||||
private static function toMilliseconds(float $time): int
|
||||
public function endTestSuite(TestSuite $suite): void
|
||||
{
|
||||
return (int) round($time * 1000);
|
||||
$suiteName = $suite->getName();
|
||||
|
||||
$this->writeNewLine();
|
||||
$this->writeNewLine();
|
||||
|
||||
$this->printEvent(self::TEST_SUITE_FINISHED, [
|
||||
self::NAME => static::isCompoundTestSuite($suite) ? $suiteName : substr($suiteName, 2),
|
||||
self::LOCATION_HINT => self::PROTOCOL . (static::isCompoundTestSuite($suite) ? $suiteName : $suiteName::__getFileName()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Test|Testable $test
|
||||
*/
|
||||
public function startTest(Test $test): void
|
||||
{
|
||||
if (!TeamCity::isPestTest($test)) {
|
||||
$this->phpunitTeamCity->startTest($test);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->printEvent(self::TEST_STARTED, [
|
||||
self::NAME => $test->getName(),
|
||||
// @phpstan-ignore-next-line
|
||||
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the given test suite is a valid Pest suite.
|
||||
*
|
||||
* @param TestSuite<Test> $suite
|
||||
*/
|
||||
private static function isPestTestSuite(TestSuite $suite): bool
|
||||
{
|
||||
return strncmp($suite->getName(), 'P\\', strlen('P\\')) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the test suite is made up of multiple smaller test suites.
|
||||
*
|
||||
* @param TestSuite<Test> $suite
|
||||
*/
|
||||
private static function isCompoundTestSuite(TestSuite $suite): bool
|
||||
{
|
||||
return file_exists($suite->getName()) || !method_exists($suite->getName(), '__getFileName');
|
||||
}
|
||||
|
||||
public static function isPestTest(Test $test): bool
|
||||
@ -210,4 +223,75 @@ final class TeamCity extends DefaultResultPrinter
|
||||
|
||||
return in_array(Testable::class, $uses, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Test|Testable $test
|
||||
*/
|
||||
public function endTest(Test $test, float $time): void
|
||||
{
|
||||
$this->printEvent(self::TEST_FINISHED, [
|
||||
self::NAME => $test->getName(),
|
||||
self::DURATION => self::toMilliseconds($time),
|
||||
]);
|
||||
|
||||
if (!$this->lastTestFailed) {
|
||||
$this->writeSuccess($test->getName());
|
||||
}
|
||||
|
||||
$this->numAssertions += $test instanceof TestCase ? $test->getNumAssertions() : 1;
|
||||
$this->lastTestFailed = false;
|
||||
}
|
||||
|
||||
private static function toMilliseconds(float $time): int
|
||||
{
|
||||
return (int) round($time * 1000);
|
||||
}
|
||||
|
||||
public function addError(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->markAsFailure($t);
|
||||
$this->writeError($test->getName());
|
||||
$this->phpunitTeamCity->addError($test, $t, $time);
|
||||
}
|
||||
|
||||
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
|
||||
{
|
||||
$this->markAsFailure($e);
|
||||
$this->writeError($test->getName());
|
||||
$this->phpunitTeamCity->addFailure($test, $e, $time);
|
||||
}
|
||||
|
||||
public function addWarning(Test $test, Warning $e, float $time): void
|
||||
{
|
||||
$this->markAsFailure($e);
|
||||
$this->writeWarning($test->getName());
|
||||
$this->phpunitTeamCity->addWarning($test, $e, $time);
|
||||
}
|
||||
|
||||
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->markAsFailure($t);
|
||||
$this->writeWarning($test->getName());
|
||||
$this->phpunitTeamCity->addIncompleteTest($test, $t, $time);
|
||||
}
|
||||
|
||||
public function addRiskyTest(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->markAsFailure($t);
|
||||
$this->writeWarning($test->getName());
|
||||
$this->phpunitTeamCity->addRiskyTest($test, $t, $time);
|
||||
}
|
||||
|
||||
public function addSkippedTest(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->markAsFailure($t);
|
||||
$this->writeWarning($test->getName());
|
||||
$this->phpunitTeamCity->printIgnoredTest($test->getName(), $t, $time);
|
||||
}
|
||||
|
||||
private function markAsFailure(Throwable $t): void
|
||||
{
|
||||
$this->lastTestFailed = true;
|
||||
ExceptionTrace::removePestReferences($t);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,8 @@ final class OppositeExpectation
|
||||
* Handle dynamic method calls into the original expectation.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return Expectation|never
|
||||
*/
|
||||
public function __call(string $name, array $arguments): Expectation
|
||||
{
|
||||
@ -61,12 +63,13 @@ final class OppositeExpectation
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$this->throwExpectationFailedException($name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic properties gets into the original expectation.
|
||||
*
|
||||
* @return Expectation|never
|
||||
*/
|
||||
public function __get(string $name): Expectation
|
||||
{
|
||||
@ -77,7 +80,6 @@ final class OppositeExpectation
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$this->throwExpectationFailedException($name);
|
||||
}
|
||||
|
||||
@ -85,6 +87,8 @@ final class OppositeExpectation
|
||||
* Creates a new expectation failed exception with a nice readable message.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
private function throwExpectationFailedException(string $name, array $arguments = []): void
|
||||
{
|
||||
|
||||
@ -7,14 +7,15 @@ namespace Pest\PendingObjects;
|
||||
use Closure;
|
||||
use Pest\Factories\TestCaseFactory;
|
||||
use Pest\Support\Backtrace;
|
||||
use Pest\Support\HigherOrderCallables;
|
||||
use Pest\Support\NullClosure;
|
||||
use Pest\TestSuite;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
/**
|
||||
* @method \Pest\Expectations\Expectation expect(mixed $value)
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @mixin HigherOrderCallables
|
||||
*/
|
||||
final class TestCall
|
||||
{
|
||||
@ -58,11 +59,15 @@ final class TestCall
|
||||
/**
|
||||
* Asserts that the test throws the given `$exceptionClass` when called.
|
||||
*/
|
||||
public function throws(string $exceptionClass, string $exceptionMessage = null): TestCall
|
||||
public function throws(string $exception, string $exceptionMessage = null): TestCall
|
||||
{
|
||||
if (class_exists($exception)) {
|
||||
$this->testCaseFactory
|
||||
->proxies
|
||||
->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exceptionClass]);
|
||||
->add(Backtrace::file(), Backtrace::line(), 'expectException', [$exception]);
|
||||
} else {
|
||||
$exceptionMessage = $exception;
|
||||
}
|
||||
|
||||
if (is_string($exceptionMessage)) {
|
||||
$this->testCaseFactory
|
||||
@ -73,11 +78,31 @@ final class TestCall
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the test throws the given `$exceptionClass` when called if the given condition is true.
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
*/
|
||||
public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall
|
||||
{
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: static function () use ($condition): bool {
|
||||
return (bool) $condition; // @phpstan-ignore-line
|
||||
};
|
||||
|
||||
if ($condition()) {
|
||||
return $this->throws($exception, $exceptionMessage);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current test multiple times with
|
||||
* each item of the given `iterable`.
|
||||
*
|
||||
* @param array<\Closure|iterable<int|string, mixed>|string> $data
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $data
|
||||
*/
|
||||
public function with(...$data): TestCall
|
||||
{
|
||||
@ -135,7 +160,7 @@ final class TestCall
|
||||
|
||||
$condition = is_callable($condition)
|
||||
? $condition
|
||||
: function () use ($condition) { /* @phpstan-ignore-line */
|
||||
: function () use ($condition) {
|
||||
return $condition;
|
||||
};
|
||||
|
||||
@ -143,32 +168,53 @@ final class TestCall
|
||||
? $conditionOrMessage
|
||||
: $message;
|
||||
|
||||
if ($condition() !== false) {
|
||||
/** @var callable(): bool $condition */
|
||||
$condition = $condition->bindTo(null);
|
||||
|
||||
$this->testCaseFactory
|
||||
->chains
|
||||
->add(Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]);
|
||||
}
|
||||
->addWhen($condition, Backtrace::file(), Backtrace::line(), 'markTestSkipped', [$message]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the property accessors to be used on the target.
|
||||
*/
|
||||
public function __get(string $name): self
|
||||
{
|
||||
return $this->addChain(Backtrace::file(), Backtrace::line(), $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the calls to be used on the target.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
*/
|
||||
public function __call(string $name, array $arguments): self
|
||||
{
|
||||
return $this->addChain(Backtrace::file(), Backtrace::line(), $name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a chain to the test case factory. Omitting the arguments will treat it as a property accessor.
|
||||
*
|
||||
* @param array<int, mixed>|null $arguments
|
||||
*/
|
||||
private function addChain(string $file, int $line, string $name, array $arguments = null): self
|
||||
{
|
||||
$this->testCaseFactory
|
||||
->chains
|
||||
->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
|
||||
->add($file, $line, $name, $arguments);
|
||||
|
||||
if ($this->descriptionLess) {
|
||||
$exporter = new Exporter();
|
||||
if ($this->testCaseFactory->description !== null) {
|
||||
$this->testCaseFactory->description .= ' → ';
|
||||
}
|
||||
$this->testCaseFactory->description .= sprintf('%s %s', $name, $exporter->shortenedRecursiveExport($arguments));
|
||||
$this->testCaseFactory->description .= $arguments === null
|
||||
? $name
|
||||
: sprintf('%s %s', $name, $exporter->shortenedRecursiveExport($arguments));
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@ -6,7 +6,7 @@ namespace Pest;
|
||||
|
||||
function version(): string
|
||||
{
|
||||
return '1.7.0';
|
||||
return '1.22.3';
|
||||
}
|
||||
|
||||
function testDirectory(string $file = ''): string
|
||||
|
||||
@ -81,7 +81,6 @@ final class Coverage implements AddsOutput, HandlesArguments
|
||||
}
|
||||
|
||||
if ($input->getOption(self::MIN_OPTION) !== null) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->coverageMin = (float) $input->getOption(self::MIN_OPTION);
|
||||
}
|
||||
|
||||
|
||||
67
src/Plugins/Environment.php
Normal file
67
src/Plugins/Environment.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Plugins;
|
||||
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Environment implements HandlesArguments
|
||||
{
|
||||
/**
|
||||
* The continuous integration environment.
|
||||
*/
|
||||
public const CI = 'ci';
|
||||
|
||||
/**
|
||||
* The local environment.
|
||||
*/
|
||||
public const LOCAL = 'local';
|
||||
|
||||
/**
|
||||
* @var \Pest\Plugins\Environment|null
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* The current environment.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private static $name;
|
||||
|
||||
/**
|
||||
* Allows to handle custom command line arguments.
|
||||
*
|
||||
* @param array<int, string> $arguments
|
||||
*
|
||||
* @return array<int, string> the updated list of arguments
|
||||
*/
|
||||
public function handleArguments(array $arguments): array
|
||||
{
|
||||
foreach ($arguments as $index => $argument) {
|
||||
if ($argument === '--ci') {
|
||||
unset($arguments[$index]);
|
||||
|
||||
self::$name = self::CI;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the environment name.
|
||||
*/
|
||||
public static function name(string $name = null): string
|
||||
{
|
||||
if (is_string($name)) {
|
||||
self::$name = $name;
|
||||
}
|
||||
|
||||
return self::$name ?? self::LOCAL;
|
||||
}
|
||||
}
|
||||
@ -5,9 +5,10 @@ declare(strict_types=1);
|
||||
namespace Pest\Plugins;
|
||||
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
use function Pest\version;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function Pest\version;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@ -5,11 +5,14 @@ declare(strict_types=1);
|
||||
namespace Pest\Repositories;
|
||||
|
||||
use Closure;
|
||||
use Pest\Exceptions\DatasetMissing;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\Exceptions\TestAlreadyExist;
|
||||
use Pest\Exceptions\TestCaseAlreadyInUse;
|
||||
use Pest\Exceptions\TestCaseClassOrTraitNotFound;
|
||||
use Pest\Factories\TestCaseFactory;
|
||||
use Pest\Plugins\Environment;
|
||||
use Pest\Support\Reflection;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -20,7 +23,7 @@ use PHPUnit\Framework\TestCase;
|
||||
final class TestRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private const SEPARATOR = '>>>';
|
||||
|
||||
@ -42,6 +45,20 @@ final class TestRepository
|
||||
return count($this->state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename of each test that should be executed in the suite.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getFilenames(): array
|
||||
{
|
||||
$testsWithOnly = $this->testsUsingOnly();
|
||||
|
||||
return array_values(array_map(function (TestCaseFactory $factory): string {
|
||||
return $factory->filename;
|
||||
}, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given callable foreach test case.
|
||||
*/
|
||||
@ -83,9 +100,7 @@ final class TestRepository
|
||||
}
|
||||
}
|
||||
|
||||
$onlyState = array_filter($this->state, function ($testFactory): bool {
|
||||
return $testFactory->only;
|
||||
});
|
||||
$onlyState = $this->testsUsingOnly();
|
||||
|
||||
$state = count($onlyState) > 0 ? $onlyState : $this->state;
|
||||
|
||||
@ -98,6 +113,22 @@ final class TestRepository
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all tests that have called the only method.
|
||||
*
|
||||
* @return array<TestCaseFactory>
|
||||
*/
|
||||
private function testsUsingOnly(): array
|
||||
{
|
||||
if (Environment::name() === Environment::CI) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_filter($this->state, function ($testFactory): bool {
|
||||
return $testFactory->only;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the given `$testCaseClass` on the given `$paths`.
|
||||
*
|
||||
@ -140,6 +171,14 @@ final class TestRepository
|
||||
throw new TestAlreadyExist($test->filename, $test->description);
|
||||
}
|
||||
|
||||
if (!$test->receivesArguments()) {
|
||||
$arguments = Reflection::getFunctionArguments($test->test);
|
||||
|
||||
if (count($arguments) > 0) {
|
||||
throw new DatasetMissing($test->filename, $test->description, $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
$this->state[sprintf('%s%s%s', $test->filename, self::SEPARATOR, $test->description)] = $test;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ final class Backtrace
|
||||
$current = null;
|
||||
|
||||
foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) {
|
||||
assert(array_key_exists(self::FILE, $trace));
|
||||
if (Str::endsWith($trace[self::FILE], (string) realpath('vendor/phpunit/phpunit/src/Util/FileLoader.php'))) {
|
||||
break;
|
||||
}
|
||||
@ -45,7 +46,11 @@ final class Backtrace
|
||||
*/
|
||||
public static function file(): string
|
||||
{
|
||||
return debug_backtrace(self::BACKTRACE_OPTIONS)[1][self::FILE];
|
||||
$trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1];
|
||||
|
||||
assert(array_key_exists(self::FILE, $trace));
|
||||
|
||||
return $trace[self::FILE];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,7 +58,11 @@ final class Backtrace
|
||||
*/
|
||||
public static function dirname(): string
|
||||
{
|
||||
return dirname(debug_backtrace(self::BACKTRACE_OPTIONS)[1][self::FILE]);
|
||||
$trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1];
|
||||
|
||||
assert(array_key_exists(self::FILE, $trace));
|
||||
|
||||
return dirname($trace[self::FILE]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,6 +70,10 @@ final class Backtrace
|
||||
*/
|
||||
public static function line(): int
|
||||
{
|
||||
return debug_backtrace(self::BACKTRACE_OPTIONS)[1]['line'];
|
||||
$trace = debug_backtrace(self::BACKTRACE_OPTIONS)[1];
|
||||
|
||||
assert(array_key_exists('line', $trace));
|
||||
|
||||
return $trace['line'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,14 +30,21 @@ final class Coverage
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs true there is any code
|
||||
* coverage driver available.
|
||||
* Runs true there is any code coverage driver available.
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return (new Runtime())->canCollectCodeCoverage();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is using Xdebug.
|
||||
*/
|
||||
public static function usingXdebug(): bool
|
||||
{
|
||||
return (new Runtime())->hasXdebug();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the code coverage report to the
|
||||
* console and returns the result in float.
|
||||
@ -45,6 +52,14 @@ final class Coverage
|
||||
public static function report(OutputInterface $output): float
|
||||
{
|
||||
if (!file_exists($reportPath = self::getPath())) {
|
||||
if (self::usingXdebug()) {
|
||||
$output->writeln(
|
||||
" <fg=black;bg=yellow;options=bold> WARN </> Unable to get coverage using Xdebug. Did you set <href=https://xdebug.org/docs/code_coverage#mode>Xdebug's coverage mode</>?</>",
|
||||
);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath));
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Pest\Support;
|
||||
|
||||
use Closure;
|
||||
use ReflectionProperty;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@ -36,4 +37,30 @@ final class ExceptionTrace
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any item from the stack trace referencing Pest so as not to
|
||||
* crowd the error log for the end user.
|
||||
*/
|
||||
public static function removePestReferences(Throwable $t): void
|
||||
{
|
||||
if (!property_exists($t, 'serializableTrace')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$property = new ReflectionProperty($t, 'serializableTrace');
|
||||
$property->setAccessible(true);
|
||||
$trace = $property->getValue($t);
|
||||
|
||||
$cleanedTrace = [];
|
||||
foreach ($trace as $item) {
|
||||
if (key_exists('file', $item) && mb_strpos($item['file'], 'vendor/pestphp/pest/') > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cleanedTrace[] = $item;
|
||||
}
|
||||
|
||||
$property->setValue($t, $cleanedTrace);
|
||||
}
|
||||
}
|
||||
|
||||
66
src/Support/HigherOrderCallables.php
Normal file
66
src/Support/HigherOrderCallables.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Support;
|
||||
|
||||
use Closure;
|
||||
use Pest\Expectation;
|
||||
use Pest\PendingObjects\TestCall;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HigherOrderCallables
|
||||
{
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
private $target;
|
||||
|
||||
public function __construct(object $target)
|
||||
{
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TValue
|
||||
*
|
||||
* Create a new expectation. Callable values will be executed prior to returning the new expectation.
|
||||
*
|
||||
* @param callable|TValue $value
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function expect($value)
|
||||
{
|
||||
return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TValue
|
||||
*
|
||||
* Create a new expectation. Callable values will be executed prior to returning the new expectation.
|
||||
*
|
||||
* @param callable|TValue $value
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function and($value)
|
||||
{
|
||||
return $this->expect($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap into the test case to perform an action and return the test case.
|
||||
*
|
||||
* @return TestCall|TestCase|object
|
||||
*/
|
||||
public function tap(callable $callable)
|
||||
{
|
||||
Reflection::bindCallableWithData($callable);
|
||||
|
||||
return $this->target;
|
||||
}
|
||||
}
|
||||
@ -4,9 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Support;
|
||||
|
||||
use Closure;
|
||||
use ReflectionClass;
|
||||
use Throwable;
|
||||
|
||||
use const PHP_MAJOR_VERSION;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -33,33 +36,40 @@ final class HigherOrderMessage
|
||||
public $line;
|
||||
|
||||
/**
|
||||
* The method name.
|
||||
* The method or property name to access.
|
||||
*
|
||||
* @readonly
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $methodName;
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The arguments.
|
||||
*
|
||||
* @var array<int, mixed>
|
||||
* @var array<int, mixed>|null
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
public $arguments;
|
||||
|
||||
/**
|
||||
* An optional condition that will determine if the message will be executed.
|
||||
*
|
||||
* @var callable(): bool|null
|
||||
*/
|
||||
public $condition = null;
|
||||
|
||||
/**
|
||||
* Creates a new higher order message.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
* @param array<int, mixed>|null $arguments
|
||||
*/
|
||||
public function __construct(string $filename, int $line, string $methodName, array $arguments)
|
||||
public function __construct(string $filename, int $line, string $methodName, $arguments)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->line = $line;
|
||||
$this->methodName = $methodName;
|
||||
$this->name = $methodName;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
@ -70,27 +80,61 @@ final class HigherOrderMessage
|
||||
*/
|
||||
public function call(object $target)
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
if ($this->hasHigherOrderCallable()) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
return (new HigherOrderCallables($target))->{$this->name}(...$this->arguments);
|
||||
}
|
||||
|
||||
try {
|
||||
return Reflection::call($target, $this->methodName, $this->arguments);
|
||||
return is_array($this->arguments)
|
||||
? Reflection::call($target, $this->name, $this->arguments)
|
||||
: $target->{$this->name}; /* @phpstan-ignore-line */
|
||||
} catch (Throwable $throwable) {
|
||||
Reflection::setPropertyValue($throwable, 'file', $this->filename);
|
||||
Reflection::setPropertyValue($throwable, 'line', $this->line);
|
||||
|
||||
if ($throwable->getMessage() === self::getUndefinedMethodMessage($target, $this->methodName)) {
|
||||
if ($throwable->getMessage() === self::getUndefinedMethodMessage($target, $this->name)) {
|
||||
/** @var ReflectionClass $reflection */
|
||||
$reflection = new ReflectionClass($target);
|
||||
/* @phpstan-ignore-next-line */
|
||||
$reflection = $reflection->getParentClass() ?: $reflection;
|
||||
Reflection::setPropertyValue($throwable, 'message', sprintf('Call to undefined method %s::%s()', $reflection->getName(), $this->methodName));
|
||||
Reflection::setPropertyValue($throwable, 'message', sprintf('Call to undefined method %s::%s()', $reflection->getName(), $this->name));
|
||||
}
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this message should only be called when the given condition is true.
|
||||
*
|
||||
* @param callable(): bool $condition
|
||||
*/
|
||||
public function when(callable $condition): self
|
||||
{
|
||||
$this->condition = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not there exists a higher order callable with the message name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasHigherOrderCallable()
|
||||
{
|
||||
return in_array($this->name, get_class_methods(HigherOrderCallables::class), true);
|
||||
}
|
||||
|
||||
private static function getUndefinedMethodMessage(object $target, string $methodName): string
|
||||
{
|
||||
if (\PHP_MAJOR_VERSION >= 8) {
|
||||
if (PHP_MAJOR_VERSION >= 8) {
|
||||
return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', get_class($target), $methodName)));
|
||||
}
|
||||
|
||||
|
||||
@ -17,11 +17,21 @@ final class HigherOrderMessageCollection
|
||||
/**
|
||||
* Adds a new higher order message to the collection.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
* @param array<int, mixed>|null $arguments
|
||||
*/
|
||||
public function add(string $filename, int $line, string $methodName, array $arguments): void
|
||||
public function add(string $filename, int $line, string $name, array $arguments = null): void
|
||||
{
|
||||
$this->messages[] = new HigherOrderMessage($filename, $line, $methodName, $arguments);
|
||||
$this->messages[] = new HigherOrderMessage($filename, $line, $name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new higher order message to the collection if the callable condition is does not return false.
|
||||
*
|
||||
* @param array<int, mixed>|null $arguments
|
||||
*/
|
||||
public function addWhen(callable $condition, string $filename, int $line, string $name, array $arguments = null): void
|
||||
{
|
||||
$this->messages[] = (new HigherOrderMessage($filename, $line, $name, $arguments))->when($condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,4 +53,20 @@ final class HigherOrderMessageCollection
|
||||
$message->call($target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of messages with the given name.
|
||||
*
|
||||
* @param string $name A higher order message name (usually a method name)
|
||||
*/
|
||||
public function count(string $name): int
|
||||
{
|
||||
return array_reduce(
|
||||
$this->messages,
|
||||
static function (int $total, HigherOrderMessage $message) use ($name): int {
|
||||
return $total + (int) ($name === $message->name);
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Support;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use Throwable;
|
||||
|
||||
@ -17,16 +18,14 @@ final class HigherOrderTapProxy
|
||||
/**
|
||||
* The target being tapped.
|
||||
*
|
||||
* @var mixed
|
||||
* @var TestCase
|
||||
*/
|
||||
public $target;
|
||||
|
||||
/**
|
||||
* Create a new tap proxy instance.
|
||||
*
|
||||
* @param mixed $target
|
||||
*/
|
||||
public function __construct($target)
|
||||
public function __construct(TestCase $target)
|
||||
{
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionUnionType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -41,15 +42,40 @@ final class Reflection
|
||||
}
|
||||
|
||||
if (is_callable($method)) {
|
||||
return Closure::fromCallable($method)->bindTo(
|
||||
TestSuite::getInstance()->test
|
||||
)(...$args);
|
||||
return static::bindCallable($method, $args);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a callable to the TestCase and return the result.
|
||||
*
|
||||
* @param array<int, mixed> $args
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function bindCallable(callable $callable, array $args = [])
|
||||
{
|
||||
return Closure::fromCallable($callable)->bindTo(TestSuite::getInstance()->test)(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a callable to the TestCase and return the result,
|
||||
* passing in the current dataset values as arguments.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function bindCallableWithData(callable $callable)
|
||||
{
|
||||
$test = TestSuite::getInstance()->test;
|
||||
|
||||
return $test === null
|
||||
? static::bindCallable($callable)
|
||||
: Closure::fromCallable($callable)->bindTo($test)(...$test->getProvidedData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers the file name from the given closure.
|
||||
*/
|
||||
@ -84,10 +110,6 @@ final class Reflection
|
||||
}
|
||||
}
|
||||
|
||||
if ($reflectionProperty === null) {
|
||||
throw ShouldNotHappen::fromMessage('Reflection property not found.');
|
||||
}
|
||||
|
||||
$reflectionProperty->setAccessible(true);
|
||||
|
||||
return $reflectionProperty->getValue($object);
|
||||
@ -118,10 +140,6 @@ final class Reflection
|
||||
}
|
||||
}
|
||||
|
||||
if ($reflectionProperty === null) {
|
||||
throw ShouldNotHappen::fromMessage('Reflection property not found.');
|
||||
}
|
||||
|
||||
$reflectionProperty->setAccessible(true);
|
||||
$reflectionProperty->setValue($object, $value);
|
||||
}
|
||||
@ -153,4 +171,37 @@ final class Reflection
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a map of function argument names to their types.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getFunctionArguments(Closure $function): array
|
||||
{
|
||||
$parameters = (new ReflectionFunction($function))->getParameters();
|
||||
$arguments = [];
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
/** @var ReflectionNamedType|ReflectionUnionType|null $types */
|
||||
$types = ($parameter->hasType()) ? $parameter->getType() : null;
|
||||
|
||||
if (is_null($types)) {
|
||||
$arguments[$parameter->getName()] = 'mixed';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$arguments[$parameter->getName()] = implode('|', array_map(
|
||||
static function (ReflectionNamedType $type): string {
|
||||
return $type->getName();
|
||||
},
|
||||
($types instanceof ReflectionNamedType)
|
||||
? [$types] // NOTE: normalize as list of to handle unions
|
||||
: $types->getTypes(),
|
||||
));
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,10 @@
|
||||
|
|
||||
*/
|
||||
|
||||
uses(Tests\TestCase::class)->in('Feature');
|
||||
uses(
|
||||
Tests\TestCase::class,
|
||||
// Illuminate\Foundation\Testing\RefreshDatabase::class,
|
||||
)->in('Feature');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('{name}', function () {
|
||||
assertTrue(true);
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
|
||||
@ -96,11 +96,29 @@
|
||||
✓ more than two datasets with (2) / (4) / (5)
|
||||
✓ more than two datasets with (2) / (4) / (6)
|
||||
✓ more than two datasets did the job right
|
||||
✓ eager registered wrapped datasets with Generator functions with (1)
|
||||
✓ eager registered wrapped datasets with Generator functions with (2)
|
||||
✓ eager registered wrapped datasets with Generator functions with (3)
|
||||
✓ eager registered wrapped datasets with Generator functions with (4)
|
||||
✓ eager registered wrapped datasets with Generator functions did the job right
|
||||
✓ eager registered wrapped datasets with Generator functions display description with data set "taylor"
|
||||
✓ eager registered wrapped datasets with Generator functions display description with data set "james"
|
||||
✓ it can resolve a dataset after the test case is available with (Closure Object (...))
|
||||
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #1
|
||||
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #2
|
||||
✓ it can resolve a dataset after the test case is available with shared array sets with (Closure Object (...)) #1
|
||||
✓ it can resolve a dataset after the test case is available with shared array sets with (Closure Object (...)) #2
|
||||
|
||||
PASS Tests\Features\Exceptions
|
||||
✓ it gives access the the underlying expectException
|
||||
✓ it catch exceptions
|
||||
✓ it catch exceptions and messages
|
||||
✓ it can just define the message
|
||||
✓ it not catch exceptions if given condition is false
|
||||
✓ it catch exceptions if given condition is true
|
||||
✓ it catch exceptions and messages if given condition is true
|
||||
✓ it can just define the message if given condition is true
|
||||
✓ it can just define the message if given condition is 1
|
||||
|
||||
PASS Tests\Features\Expect\HigherOrder\methods
|
||||
✓ it can access methods
|
||||
@ -112,10 +130,14 @@
|
||||
✓ it works with sequence
|
||||
✓ it can compose complex expectations
|
||||
✓ it can handle nested method calls
|
||||
✓ it works with higher order tests
|
||||
|
||||
PASS Tests\Features\Expect\HigherOrder\methodsAndProperties
|
||||
✓ it can access methods and properties
|
||||
✓ it can handle nested methods and properties
|
||||
✓ it works with higher order tests
|
||||
✓ it can start a new higher order expectation using the and syntax
|
||||
✓ it can start a new higher order expectation using the and syntax in higher order tests
|
||||
|
||||
PASS Tests\Features\Expect\HigherOrder\properties
|
||||
✓ it allows properties to be accessed from the value
|
||||
@ -127,6 +149,7 @@
|
||||
✓ it can compose complex expectations
|
||||
✓ it works with objects
|
||||
✓ it works with nested properties
|
||||
✓ it works with higher order tests
|
||||
|
||||
PASS Tests\Features\Expect\each
|
||||
✓ an exception is thrown if the the type is not iterable
|
||||
@ -147,6 +170,17 @@
|
||||
✓ it properly parses json string
|
||||
✓ fails with broken json string
|
||||
|
||||
PASS Tests\Features\Expect\matchExpectation
|
||||
✓ it pass
|
||||
✓ it failures
|
||||
✓ it runs with truthy
|
||||
✓ it runs with falsy
|
||||
✓ it runs with truthy closure condition
|
||||
✓ it runs with falsy closure condition
|
||||
✓ it can be passed non-callable values
|
||||
✓ it fails with unhandled match
|
||||
✓ it can be used in higher order tests
|
||||
|
||||
PASS Tests\Features\Expect\not
|
||||
✓ not property calls
|
||||
|
||||
@ -157,7 +191,7 @@
|
||||
✓ an exception is thrown if the the type is not iterable
|
||||
✓ allows for sequences of checks to be run on iterable data
|
||||
✓ loops back to the start if it runs out of sequence items
|
||||
✓ it works if the number of items in the iterable is smaller than the number of expectations
|
||||
✓ fails if the number of iterable items is greater than the number of expectations
|
||||
✓ it works with associative arrays
|
||||
✓ it can be passed non-callable values
|
||||
✓ it can be passed a mixture of value types
|
||||
@ -195,6 +229,20 @@
|
||||
PASS Tests\Features\Expect\toBeFalse
|
||||
✓ strict comparisons
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeFalsy
|
||||
✓ passes as falsy with (false)
|
||||
✓ passes as falsy with ('')
|
||||
✓ passes as falsy with (null)
|
||||
✓ passes as falsy with (0)
|
||||
✓ passes as falsy with ('0')
|
||||
✓ passes as not falsy with (true)
|
||||
✓ passes as not falsy with (1) #1
|
||||
✓ passes as not falsy with ('false')
|
||||
✓ passes as not falsy with (1) #2
|
||||
✓ passes as not falsy with (-1)
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeFile
|
||||
@ -215,6 +263,11 @@
|
||||
PASS Tests\Features\Expect\toBeGreatherThanOrEqual
|
||||
✓ passes
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeIn
|
||||
✓ passes
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeInfinite
|
||||
@ -300,6 +353,20 @@
|
||||
PASS Tests\Features\Expect\toBeTrue
|
||||
✓ strict comparisons
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeTruthy
|
||||
✓ passes as truthy with (true)
|
||||
✓ passes as truthy with (1) #1
|
||||
✓ passes as truthy with ('false')
|
||||
✓ passes as truthy with (1) #2
|
||||
✓ passes as truthy with (-1)
|
||||
✓ passes as not truthy with (false)
|
||||
✓ passes as not truthy with ('')
|
||||
✓ passes as not truthy with (null)
|
||||
✓ passes as not truthy with (0)
|
||||
✓ passes as not truthy with ('0')
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeWritableDirectory
|
||||
@ -314,9 +381,16 @@
|
||||
|
||||
PASS Tests\Features\Expect\toContain
|
||||
✓ passes strings
|
||||
✓ passes strings with multiple needles
|
||||
✓ passes arrays
|
||||
✓ passes arrays with multiple needles
|
||||
✓ passes with array needles
|
||||
✓ failures
|
||||
✓ failures with multiple needles (all failing)
|
||||
✓ failures with multiple needles (some failing)
|
||||
✓ not failures
|
||||
✓ not failures with multiple needles (all failing)
|
||||
✓ not failures with multiple needles (some failing)
|
||||
|
||||
PASS Tests\Features\Expect\toEndWith
|
||||
✓ pass
|
||||
@ -366,6 +440,25 @@
|
||||
PASS Tests\Features\Expect\toHaveKeys
|
||||
✓ pass
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toHaveLength
|
||||
✓ it passes with ('Fortaleza')
|
||||
✓ it passes with ('Sollefteå')
|
||||
✓ it passes with ('Ιεράπετρα')
|
||||
✓ it passes with (stdClass Object (...))
|
||||
✓ it passes with (Illuminate\Support\Collection Object (...))
|
||||
✓ it passes with array
|
||||
✓ it passes with *not*
|
||||
✓ it properly fails with *not*
|
||||
✓ it fails with (1)
|
||||
✓ it fails with (1.5)
|
||||
✓ it fails with (true)
|
||||
✓ it fails with (null)
|
||||
|
||||
PASS Tests\Features\Expect\toHaveProperties
|
||||
✓ pass
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toHaveProperty
|
||||
@ -398,6 +491,39 @@
|
||||
✓ failures
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toThrow
|
||||
✓ passes
|
||||
✓ failures 1
|
||||
✓ failures 2
|
||||
✓ failures 3
|
||||
✓ failures 4
|
||||
✓ failures 5
|
||||
✓ failures 6
|
||||
✓ failures 7
|
||||
✓ not failures
|
||||
✓ closure missing parameter
|
||||
✓ closure missing type-hint
|
||||
✓ it can handle a non-defined exception
|
||||
✓ it can handle a class not found Error
|
||||
|
||||
PASS Tests\Features\Expect\unless
|
||||
✓ it pass
|
||||
✓ it failures
|
||||
✓ it runs with truthy
|
||||
✓ it skips with falsy
|
||||
✓ it runs with truthy closure condition
|
||||
✓ it skips with falsy closure condition
|
||||
✓ it can be used in higher order tests
|
||||
|
||||
PASS Tests\Features\Expect\when
|
||||
✓ it pass
|
||||
✓ it failures
|
||||
✓ it runs with truthy
|
||||
✓ it skips with falsy
|
||||
✓ it runs with truthy closure condition
|
||||
✓ it skips with falsy closure condition
|
||||
✓ it can be used in higher order tests
|
||||
|
||||
PASS Tests\Features\Helpers
|
||||
✓ it can set/get properties on $this
|
||||
✓ it throws error if property do not exist
|
||||
@ -409,6 +535,13 @@
|
||||
PASS Tests\Features\HigherOrderTests
|
||||
✓ it proxies calls to object
|
||||
✓ it is capable doing multiple assertions
|
||||
✓ it resolves expect callables correctly
|
||||
✓ does not treat method names as callables
|
||||
✓ it can tap into the test
|
||||
✓ it can pass datasets into the expect callables with (1, 2, 3)
|
||||
✓ it can pass datasets into the tap callable with (1, 2, 3)
|
||||
✓ it can pass shared datasets into callables with (1)
|
||||
✓ it can pass shared datasets into callables with (2)
|
||||
|
||||
WARN Tests\Features\Incompleted
|
||||
… incompleted
|
||||
@ -441,6 +574,8 @@
|
||||
✓ it do not skips with falsy closure condition
|
||||
- it skips with condition and message → skipped because foo
|
||||
- it skips when skip after assertion
|
||||
- it can use something in the test case as a condition → This test was skipped
|
||||
- it can user higher order callables and skip
|
||||
|
||||
PASS Tests\Features\Test
|
||||
✓ a test
|
||||
@ -454,12 +589,14 @@
|
||||
|
||||
PASS Tests\Hooks\AfterAllTest
|
||||
✓ global afterAll execution order
|
||||
✓ it only gets called once per file
|
||||
|
||||
PASS Tests\Hooks\AfterEachTest
|
||||
✓ global afterEach execution order
|
||||
|
||||
PASS Tests\Hooks\BeforeAllTest
|
||||
✓ global beforeAll execution order
|
||||
✓ it only gets called once per file
|
||||
|
||||
PASS Tests\Hooks\BeforeEachTest
|
||||
✓ global beforeEach execution order
|
||||
@ -529,6 +666,10 @@
|
||||
✓ it show the actual dataset of multiple non-named datasets in their description
|
||||
✓ it show the correct description for mixed named and not-named datasets
|
||||
|
||||
PASS Tests\Unit\Plugins\Environment
|
||||
✓ environment is set to CI when --ci option is used
|
||||
✓ environment is set to Local when --ci option is not used
|
||||
|
||||
PASS Tests\Unit\Plugins\Version
|
||||
✓ it outputs the version when --version is used
|
||||
✓ it do not outputs version when --version is not used
|
||||
@ -551,10 +692,17 @@
|
||||
|
||||
PASS Tests\Unit\TestSuite
|
||||
✓ it does not allow to add the same test description twice
|
||||
✓ it alerts users about tests with arguments but no input
|
||||
✓ it can return an array of all test suite filenames
|
||||
✓ it can filter the test suite filenames to those with the only method
|
||||
✓ it does not filter the test suite filenames to those with the only method when working in CI pipeline
|
||||
|
||||
PASS Tests\Visual\Help
|
||||
✓ visual snapshot of help command output
|
||||
|
||||
PASS Tests\Visual\JUnit
|
||||
✓ it is can successfully call all public methods
|
||||
|
||||
PASS Tests\Visual\SingleTestOrDirectory
|
||||
✓ allows to run a single test
|
||||
✓ allows to run a directory
|
||||
@ -564,6 +712,9 @@
|
||||
WARN Tests\Visual\Success
|
||||
- visual snapshot of test suite on success
|
||||
|
||||
PASS Tests\Visual\TeamCity
|
||||
✓ it is can successfully call all public methods
|
||||
|
||||
PASS Tests\Features\Depends
|
||||
✓ first
|
||||
✓ second
|
||||
@ -578,5 +729,5 @@
|
||||
✓ it is a test
|
||||
✓ it uses correct parent class
|
||||
|
||||
Tests: 4 incompleted, 7 skipped, 362 passed
|
||||
Tests: 4 incompleted, 9 skipped, 487 passed
|
||||
|
||||
11
tests/Datasets/Bound.php
Normal file
11
tests/Datasets/Bound.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
dataset('bound.closure', function () {
|
||||
yield function () { return 1; };
|
||||
yield function () { return 2; };
|
||||
});
|
||||
|
||||
dataset('bound.array', [
|
||||
function () { return 1; },
|
||||
function () { return 2; },
|
||||
]);
|
||||
@ -13,3 +13,20 @@ dataset('numbers.closure.wrapped', function () {
|
||||
dataset('numbers.array', [[1], [2]]);
|
||||
|
||||
dataset('numbers.array.wrapped', [1, 2]);
|
||||
|
||||
dataset('numbers.generators.wrapped', function () {
|
||||
yield from firstSetOfNumber();
|
||||
yield from secondSetOfNumbers();
|
||||
});
|
||||
|
||||
function firstSetOfNumber(): Generator
|
||||
{
|
||||
yield 1;
|
||||
yield 2;
|
||||
}
|
||||
|
||||
function secondSetOfNumbers(): Generator
|
||||
{
|
||||
yield 3;
|
||||
yield 4;
|
||||
}
|
||||
|
||||
@ -5,6 +5,10 @@ use Pest\Exceptions\DatasetAlreadyExist;
|
||||
use Pest\Exceptions\DatasetDoesNotExist;
|
||||
use Pest\Plugin;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->foo = 'bar';
|
||||
});
|
||||
|
||||
it('throws exception if dataset does not exist', function () {
|
||||
$this->expectException(DatasetDoesNotExist::class);
|
||||
$this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
||||
@ -223,3 +227,45 @@ test('more than two datasets', function ($text_a, $text_b, $text_c) use ($state,
|
||||
test('more than two datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324135136145146235236245246');
|
||||
});
|
||||
|
||||
$wrapped_generator_state = new stdClass();
|
||||
$wrapped_generator_state->text = '';
|
||||
$wrapped_generator_function_datasets = [1, 2, 3, 4];
|
||||
|
||||
test(
|
||||
'eager registered wrapped datasets with Generator functions',
|
||||
function (int $text) use (
|
||||
$wrapped_generator_state,
|
||||
$wrapped_generator_function_datasets
|
||||
) {
|
||||
$wrapped_generator_state->text .= $text;
|
||||
expect(in_array($text, $wrapped_generator_function_datasets))->toBe(true);
|
||||
}
|
||||
)->with('numbers.generators.wrapped');
|
||||
|
||||
test('eager registered wrapped datasets with Generator functions did the job right', function () use ($wrapped_generator_state) {
|
||||
expect($wrapped_generator_state->text)->toBe('1234');
|
||||
});
|
||||
|
||||
test('eager registered wrapped datasets with Generator functions display description', function ($wrapped_generator_state_with_description) {
|
||||
expect($wrapped_generator_state_with_description)->not->toBeEmpty();
|
||||
})->with(function () {
|
||||
yield 'taylor' => 'taylor@laravel.com';
|
||||
yield 'james' => 'james@laravel.com';
|
||||
});
|
||||
|
||||
it('can resolve a dataset after the test case is available', function ($result) {
|
||||
expect($result)->toBe('bar');
|
||||
})->with([
|
||||
function () {
|
||||
return $this->foo;
|
||||
},
|
||||
]);
|
||||
|
||||
it('can resolve a dataset after the test case is available with shared yield sets', function ($result) {
|
||||
expect($result)->toBeInt()->toBeLessThan(3);
|
||||
})->with('bound.closure');
|
||||
|
||||
it('can resolve a dataset after the test case is available with shared array sets', function ($result) {
|
||||
expect($result)->toBeInt()->toBeLessThan(3);
|
||||
})->with('bound.array');
|
||||
|
||||
@ -13,3 +13,27 @@ it('catch exceptions', function () {
|
||||
it('catch exceptions and messages', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throws(Exception::class, 'Something bad happened');
|
||||
|
||||
it('can just define the message', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throws('Something bad happened');
|
||||
|
||||
it('not catch exceptions if given condition is false', function () {
|
||||
$this->assertTrue(true);
|
||||
})->throwsIf(false, Exception::class);
|
||||
|
||||
it('catch exceptions if given condition is true', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(function () { return true; }, Exception::class);
|
||||
|
||||
it('catch exceptions and messages if given condition is true', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(true, Exception::class, 'Something bad happened');
|
||||
|
||||
it('can just define the message if given condition is true', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(true, 'Something bad happened');
|
||||
|
||||
it('can just define the message if given condition is 1', function () {
|
||||
throw new Exception('Something bad happened');
|
||||
})->throwsIf(1, 'Something bad happened');
|
||||
|
||||
@ -67,6 +67,13 @@ it('can handle nested method calls', function () {
|
||||
->books()->each->toBeArray();
|
||||
});
|
||||
|
||||
it('works with higher order tests')
|
||||
->expect(new HasMethods())
|
||||
->newInstance()->newInstance()->name()->toEqual('Has Methods')->toBeString()
|
||||
->newInstance()->name()->toEqual('Has Methods')->not->toBeArray
|
||||
->name()->toEqual('Has Methods')
|
||||
->books()->each->toBeArray;
|
||||
|
||||
class HasMethods
|
||||
{
|
||||
public function name()
|
||||
|
||||
@ -22,6 +22,32 @@ it('can handle nested methods and properties', function () {
|
||||
->newInstance()->books()->toBeArray();
|
||||
});
|
||||
|
||||
it('works with higher order tests')
|
||||
->expect(new HasMethodsAndProperties())
|
||||
->meta->foo->bar->toBeString()->toEqual('baz')->not->toBeInt
|
||||
->newInstance()->meta->foo->toBeArray
|
||||
->newInstance()->multiply(2, 2)->toEqual(4)->not->toEqual(5)
|
||||
->newInstance()->books()->toBeArray();
|
||||
|
||||
it('can start a new higher order expectation using the and syntax', function () {
|
||||
expect(new HasMethodsAndProperties())
|
||||
->toBeInstanceOf(HasMethodsAndProperties::class)
|
||||
->meta->toBeArray
|
||||
->and(['foo' => 'bar'])
|
||||
->toBeArray()
|
||||
->foo->toEqual('bar');
|
||||
|
||||
expect(static::getCount())->toEqual(4);
|
||||
});
|
||||
|
||||
it('can start a new higher order expectation using the and syntax in higher order tests')
|
||||
->expect(new HasMethodsAndProperties())
|
||||
->toBeInstanceOf(HasMethodsAndProperties::class)
|
||||
->meta->toBeArray
|
||||
->and(['foo' => 'bar'])
|
||||
->toBeArray()
|
||||
->foo->toEqual('bar');
|
||||
|
||||
class HasMethodsAndProperties
|
||||
{
|
||||
public $name = 'Has Methods and Properties';
|
||||
|
||||
@ -64,6 +64,11 @@ it('works with nested properties', function () {
|
||||
->posts->toBeArray()->toHaveCount(2);
|
||||
});
|
||||
|
||||
it('works with higher order tests')
|
||||
->expect(new HasProperties())
|
||||
->nested->foo->bar->toBeString()->toEqual('baz')
|
||||
->posts->toBeArray()->toHaveCount(2);
|
||||
|
||||
class HasProperties
|
||||
{
|
||||
public $name = 'foo';
|
||||
|
||||
148
tests/Features/Expect/matchExpectation.php
Normal file
148
tests/Features/Expect/matchExpectation.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->matched = null;
|
||||
});
|
||||
|
||||
it('pass', function () {
|
||||
expect('baz')
|
||||
->match('foo', [
|
||||
'bar' => function ($value) {
|
||||
$this->matched = 'bar';
|
||||
|
||||
return $value->toEqual('bar');
|
||||
},
|
||||
'foo' => function ($value) {
|
||||
$this->matched = 'baz';
|
||||
|
||||
return $value->toEqual('baz');
|
||||
},
|
||||
]
|
||||
)
|
||||
->toEqual($this->matched);
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('failures', function () {
|
||||
expect(true)
|
||||
->match('foo', [
|
||||
'bar' => function ($value) {
|
||||
return $value->toBeTrue();
|
||||
},
|
||||
'foo' => function ($value) {
|
||||
return $value->toBeFalse();
|
||||
},
|
||||
]
|
||||
);
|
||||
})->throws(ExpectationFailedException::class, 'true is false');
|
||||
|
||||
it('runs with truthy', function () {
|
||||
expect('foo')
|
||||
->match(1, [
|
||||
'bar' => function ($value) {
|
||||
$this->matched = 'bar';
|
||||
|
||||
return $value->toEqual('bar');
|
||||
},
|
||||
true => function ($value) {
|
||||
$this->matched = 'foo';
|
||||
|
||||
return $value->toEqual('foo');
|
||||
},
|
||||
]
|
||||
)
|
||||
->toEqual($this->matched);
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('runs with falsy', function () {
|
||||
expect('foo')
|
||||
->match(false, [
|
||||
'bar' => function ($value) {
|
||||
$this->matched = 'bar';
|
||||
|
||||
return $value->toEqual('bar');
|
||||
},
|
||||
false => function ($value) {
|
||||
$this->matched = 'foo';
|
||||
|
||||
return $value->toEqual('foo');
|
||||
},
|
||||
]
|
||||
)
|
||||
->toEqual($this->matched);
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('runs with truthy closure condition', function () {
|
||||
expect('foo')
|
||||
->match(
|
||||
function () { return '1'; }, [
|
||||
'bar' => function ($value) {
|
||||
$this->matched = 'bar';
|
||||
|
||||
return $value->toEqual('bar');
|
||||
},
|
||||
true => function ($value) {
|
||||
$this->matched = 'foo';
|
||||
|
||||
return $value->toEqual('foo');
|
||||
},
|
||||
]
|
||||
)
|
||||
->toEqual($this->matched);
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('runs with falsy closure condition', function () {
|
||||
expect('foo')
|
||||
->match(
|
||||
function () { return '0'; }, [
|
||||
'bar' => function ($value) {
|
||||
$this->matched = 'bar';
|
||||
|
||||
return $value->toEqual('bar');
|
||||
},
|
||||
false => function ($value) {
|
||||
$this->matched = 'foo';
|
||||
|
||||
return $value->toEqual('foo');
|
||||
},
|
||||
]
|
||||
)
|
||||
->toEqual($this->matched);
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('can be passed non-callable values', function () {
|
||||
expect('foo')
|
||||
->match('pest', [
|
||||
'bar' => 'foo',
|
||||
'pest' => 'baz',
|
||||
]
|
||||
);
|
||||
})->throws(ExpectationFailedException::class, 'two strings are equal');
|
||||
|
||||
it('fails with unhandled match', function () {
|
||||
expect('foo')->match('bar', []);
|
||||
})->throws(ExpectationFailedException::class, 'Unhandled match value.');
|
||||
|
||||
it('can be used in higher order tests')
|
||||
->expect(true)
|
||||
->match(
|
||||
function () { return true; }, [
|
||||
false => function ($value) {
|
||||
return $value->toBeFalse();
|
||||
},
|
||||
true => function ($value) {
|
||||
return $value->toBeTrue();
|
||||
},
|
||||
]
|
||||
);
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('an exception is thrown if the the type is not iterable', function () {
|
||||
expect('Foobar')->each->sequence();
|
||||
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
|
||||
@ -7,9 +9,15 @@ test('an exception is thrown if the the type is not iterable', function () {
|
||||
test('allows for sequences of checks to be run on iterable data', function () {
|
||||
expect([1, 2, 3])
|
||||
->sequence(
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(1);
|
||||
},
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(2);
|
||||
},
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(3);
|
||||
},
|
||||
);
|
||||
|
||||
expect(static::getCount())->toBe(6);
|
||||
@ -18,30 +26,46 @@ test('allows for sequences of checks to be run on iterable data', function () {
|
||||
test('loops back to the start if it runs out of sequence items', function () {
|
||||
expect([1, 2, 3, 1, 2, 3, 1, 2])
|
||||
->sequence(
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(1);
|
||||
},
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(2);
|
||||
},
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(3);
|
||||
},
|
||||
);
|
||||
|
||||
expect(static::getCount())->toBe(16);
|
||||
});
|
||||
|
||||
test('it works if the number of items in the iterable is smaller than the number of expectations', function () {
|
||||
test('fails if the number of iterable items is greater than the number of expectations', function () {
|
||||
expect([1, 2])
|
||||
->sequence(
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(1); },
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(2); },
|
||||
function ($expectation) { $expectation->toBeInt()->toEqual(3); },
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(1);
|
||||
},
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(2);
|
||||
},
|
||||
function ($expectation) {
|
||||
$expectation->toBeInt()->toEqual(3);
|
||||
},
|
||||
);
|
||||
|
||||
expect(static::getCount())->toBe(4);
|
||||
});
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('it works with associative arrays', function () {
|
||||
expect(['foo' => 'bar', 'baz' => 'boom'])
|
||||
->sequence(
|
||||
function ($expectation, $key) { $expectation->toEqual('bar'); $key->toEqual('foo'); },
|
||||
function ($expectation, $key) { $expectation->toEqual('boom'); $key->toEqual('baz'); },
|
||||
function ($expectation, $key) {
|
||||
$expectation->toEqual('bar');
|
||||
$key->toEqual('foo');
|
||||
},
|
||||
function ($expectation, $key) {
|
||||
$expectation->toEqual('boom');
|
||||
$key->toEqual('baz');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@ -54,7 +78,9 @@ test('it can be passed non-callable values', function () {
|
||||
test('it can be passed a mixture of value types', function () {
|
||||
expect(['foo', 'bar', 'baz'])->sequence(
|
||||
'foo',
|
||||
function ($expectation) { $expectation->toEqual('bar')->toBeString(); },
|
||||
function ($expectation) {
|
||||
$expectation->toEqual('bar')->toBeString();
|
||||
},
|
||||
'baz'
|
||||
);
|
||||
|
||||
|
||||
19
tests/Features/Expect/toBeFalsy.php
Normal file
19
tests/Features/Expect/toBeFalsy.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('passes as falsy', function ($value) {
|
||||
expect($value)->toBeFalsy();
|
||||
})->with([false, '', null, 0, '0']);
|
||||
|
||||
test('passes as not falsy', function ($value) {
|
||||
expect($value)->not->toBeFalsy();
|
||||
})->with([true, [1], 'false', 1, -1]);
|
||||
|
||||
test('failures', function () {
|
||||
expect(1)->toBeFalsy();
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
expect(null)->not->toBeFalsy();
|
||||
})->throws(ExpectationFailedException::class);
|
||||
16
tests/Features/Expect/toBeIn.php
Normal file
16
tests/Features/Expect/toBeIn.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('passes', function () {
|
||||
expect('a')->toBeIn(['a', 'b', 'c']);
|
||||
expect('d')->not->toBeIn(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
test('failures', function () {
|
||||
expect('d')->toBeIn(['a', 'b', 'c']);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
expect('a')->not->toBeIn(['a', 'b', 'c']);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
19
tests/Features/Expect/toBeTruthy.php
Normal file
19
tests/Features/Expect/toBeTruthy.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('passes as truthy', function ($value) {
|
||||
expect($value)->toBeTruthy();
|
||||
})->with([true, [1], 'false', 1, -1]);
|
||||
|
||||
test('passes as not truthy', function ($value) {
|
||||
expect($value)->not->toBeTruthy();
|
||||
})->with([false, '', null, 0, '0']);
|
||||
|
||||
test('failures', function () {
|
||||
expect(null)->toBeTruthy();
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
expect(1)->not->toBeTruthy();
|
||||
})->throws(ExpectationFailedException::class);
|
||||
@ -3,17 +3,45 @@
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('passes strings', function () {
|
||||
expect([1, 2, 42])->toContain(42);
|
||||
expect('Nuno')->toContain('Nu');
|
||||
});
|
||||
|
||||
test('passes strings with multiple needles', function () {
|
||||
expect('Nuno')->toContain('Nu', 'no');
|
||||
});
|
||||
|
||||
test('passes arrays', function () {
|
||||
expect('Nuno')->toContain('Nu');
|
||||
expect([1, 2, 42])->toContain(42);
|
||||
});
|
||||
|
||||
test('passes arrays with multiple needles', function () {
|
||||
expect([1, 2, 42])->toContain(42, 2);
|
||||
});
|
||||
|
||||
test('passes with array needles', function () {
|
||||
expect([[1, 2, 3], 2, 42])->toContain(42, [1, 2, 3]);
|
||||
});
|
||||
|
||||
test('failures', function () {
|
||||
expect([1, 2, 42])->toContain(3);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('failures with multiple needles (all failing)', function () {
|
||||
expect([1, 2, 42])->toContain(3, 4);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('failures with multiple needles (some failing)', function () {
|
||||
expect([1, 2, 42])->toContain(1, 3, 4);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
expect([1, 2, 42])->not->toContain(42);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures with multiple needles (all failing)', function () {
|
||||
expect([1, 2, 42])->not->toContain(42, 2);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures with multiple needles (some failing)', function () {
|
||||
expect([1, 2, 42])->not->toContain(42, 1);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
27
tests/Features/Expect/toHaveLength.php
Normal file
27
tests/Features/Expect/toHaveLength.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
it('passes', function ($value) {
|
||||
expect($value)->toHaveLength(9);
|
||||
})->with([
|
||||
'Fortaleza', 'Sollefteå', 'Ιεράπετρα',
|
||||
(object) [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
collect([1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
||||
]);
|
||||
|
||||
it('passes with array', function () {
|
||||
expect([1, 2, 3])->toHaveLength(3);
|
||||
});
|
||||
|
||||
it('passes with *not*', function () {
|
||||
expect('')->not->toHaveLength(1);
|
||||
});
|
||||
|
||||
it('properly fails with *not*', function () {
|
||||
expect('pest')->not->toHaveLength(4);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
it('fails', function ($value) {
|
||||
expect($value)->toHaveLength(1);
|
||||
})->with([1, 1.5, true, null])->throws(BadMethodCallException::class);
|
||||
26
tests/Features/Expect/toHaveProperties.php
Normal file
26
tests/Features/Expect/toHaveProperties.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('pass', function () {
|
||||
$object = new stdClass();
|
||||
$object->name = 'Jhon';
|
||||
$object->age = 21;
|
||||
|
||||
expect($object)->toHaveProperties(['name', 'age']);
|
||||
});
|
||||
|
||||
test('failures', function () {
|
||||
$object = new stdClass();
|
||||
$object->name = 'Jhon';
|
||||
|
||||
expect($object)->toHaveProperties(['name', 'age']);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
$object = new stdClass();
|
||||
$object->name = 'Jhon';
|
||||
$object->age = 21;
|
||||
|
||||
expect($object)->not->toHaveProperties(['name', 'age']);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
72
tests/Features/Expect/toThrow.php
Normal file
72
tests/Features/Expect/toThrow.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
test('passes', function () {
|
||||
expect(function () { throw new RuntimeException(); })->toThrow(RuntimeException::class);
|
||||
expect(function () { throw new RuntimeException(); })->toThrow(Exception::class);
|
||||
expect(function () { throw new RuntimeException(); })->toThrow(function (RuntimeException $e) {});
|
||||
expect(function () { throw new RuntimeException('actual message'); })->toThrow(function (Exception $e) {
|
||||
expect($e->getMessage())->toBe('actual message');
|
||||
});
|
||||
expect(function () {})->not->toThrow(Exception::class);
|
||||
expect(function () { throw new RuntimeException('actual message'); })->toThrow('actual message');
|
||||
expect(function () { throw new Exception(); })->not->toThrow(RuntimeException::class);
|
||||
expect(function () { throw new RuntimeException('actual message'); })->toThrow(RuntimeException::class, 'actual message');
|
||||
expect(function () { throw new RuntimeException('actual message'); })->toThrow(function (RuntimeException $e) {}, 'actual message');
|
||||
});
|
||||
|
||||
test('failures 1', function () {
|
||||
expect(function () {})->toThrow(RuntimeException::class);
|
||||
})->throws(ExpectationFailedException::class, 'Exception "' . RuntimeException::class . '" not thrown.');
|
||||
|
||||
test('failures 2', function () {
|
||||
expect(function () {})->toThrow(function (RuntimeException $e) {});
|
||||
})->throws(ExpectationFailedException::class, 'Exception "' . RuntimeException::class . '" not thrown.');
|
||||
|
||||
test('failures 3', function () {
|
||||
expect(function () { throw new Exception(); })->toThrow(function (RuntimeException $e) {});
|
||||
})->throws(ExpectationFailedException::class, 'Failed asserting that Exception Object');
|
||||
|
||||
test('failures 4', function () {
|
||||
expect(function () { throw new Exception('actual message'); })
|
||||
->toThrow(function (Exception $e) {
|
||||
expect($e->getMessage())->toBe('expected message');
|
||||
});
|
||||
})->throws(ExpectationFailedException::class, 'Failed asserting that two strings are identical');
|
||||
|
||||
test('failures 5', function () {
|
||||
expect(function () { throw new Exception('actual message'); })->toThrow('expected message');
|
||||
})->throws(ExpectationFailedException::class, 'Failed asserting that \'actual message\' contains "expected message".');
|
||||
|
||||
test('failures 6', function () {
|
||||
expect(function () {})->toThrow('actual message');
|
||||
})->throws(ExpectationFailedException::class, 'Exception with message "actual message" not thrown');
|
||||
|
||||
test('failures 7', function () {
|
||||
expect(function () { throw new RuntimeException('actual message'); })->toThrow(RuntimeException::class, 'expected message');
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('not failures', function () {
|
||||
expect(function () { throw new RuntimeException(); })->not->toThrow(RuntimeException::class);
|
||||
})->throws(ExpectationFailedException::class);
|
||||
|
||||
test('closure missing parameter', function () {
|
||||
expect(function () {})->toThrow(function () {});
|
||||
})->throws(InvalidArgumentException::class, 'The given closure must have a single parameter type-hinted as the class string.');
|
||||
|
||||
test('closure missing type-hint', function () {
|
||||
expect(function () {})->toThrow(function ($e) {});
|
||||
})->throws(InvalidArgumentException::class, 'The given closure\'s parameter must be type-hinted as the class string.');
|
||||
|
||||
it('can handle a non-defined exception', function () {
|
||||
expect(function () {
|
||||
throw new NonExistingException();
|
||||
})->toThrow(NonExistingException::class);
|
||||
})->throws(Error::class);
|
||||
|
||||
it('can handle a class not found Error', function () {
|
||||
expect(function () {
|
||||
throw new NonExistingException();
|
||||
})->toThrow(Error::class);
|
||||
});
|
||||
101
tests/Features/Expect/unless.php
Normal file
101
tests/Features/Expect/unless.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->unlessObject = new stdClass();
|
||||
$this->unlessObject->trueValue = true;
|
||||
$this->unlessObject->foo = 'foo';
|
||||
});
|
||||
|
||||
it('pass', function () {
|
||||
expect('foo')
|
||||
->unless(
|
||||
true,
|
||||
function ($value) {
|
||||
return $value->toEqual('bar');
|
||||
}
|
||||
)
|
||||
->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(1);
|
||||
});
|
||||
|
||||
it('failures', function () {
|
||||
expect('foo')
|
||||
->unless(
|
||||
false,
|
||||
function ($value) {
|
||||
return $value->toBeTrue();
|
||||
}
|
||||
)
|
||||
->toEqual('foo');
|
||||
})->throws(ExpectationFailedException::class, 'is true');
|
||||
|
||||
it('runs with truthy', function () {
|
||||
expect($this->unlessObject)
|
||||
->unless(
|
||||
0,
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeTrue();
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('skips with falsy', function () {
|
||||
expect($this->unlessObject)
|
||||
->unless(
|
||||
1,
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeFalse(); // fails
|
||||
}
|
||||
)
|
||||
->unless(
|
||||
true,
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeFalse(); // fails
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(1);
|
||||
});
|
||||
|
||||
it('runs with truthy closure condition', function () {
|
||||
expect($this->unlessObject)
|
||||
->unless(
|
||||
function () { return '0'; },
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeTrue();
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('skips with falsy closure condition', function () {
|
||||
expect($this->unlessObject)
|
||||
->unless(
|
||||
function () { return '1'; },
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeFalse(); // fails
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(1);
|
||||
});
|
||||
|
||||
it('can be used in higher order tests')
|
||||
->expect(true)
|
||||
->unless(
|
||||
function () { return false; },
|
||||
function ($value) {
|
||||
return $value->toBeFalse();
|
||||
}
|
||||
)
|
||||
->throws(ExpectationFailedException::class, 'true is false');
|
||||
101
tests/Features/Expect/when.php
Normal file
101
tests/Features/Expect/when.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->whenObject = new stdClass();
|
||||
$this->whenObject->trueValue = true;
|
||||
$this->whenObject->foo = 'foo';
|
||||
});
|
||||
|
||||
it('pass', function () {
|
||||
expect('foo')
|
||||
->when(
|
||||
true,
|
||||
function ($value) {
|
||||
return $value->toEqual('foo');
|
||||
}
|
||||
)
|
||||
->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('failures', function () {
|
||||
expect('foo')
|
||||
->when(
|
||||
true,
|
||||
function ($value) {
|
||||
return $value->toBeTrue();
|
||||
}
|
||||
)
|
||||
->toEqual('foo');
|
||||
})->throws(ExpectationFailedException::class, 'is true');
|
||||
|
||||
it('runs with truthy', function () {
|
||||
expect($this->whenObject)
|
||||
->when(
|
||||
1,
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeTrue();
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('skips with falsy', function () {
|
||||
expect($this->whenObject)
|
||||
->when(
|
||||
0,
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeFalse(); // fails
|
||||
}
|
||||
)
|
||||
->when(
|
||||
false,
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeFalse(); // fails
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(1);
|
||||
});
|
||||
|
||||
it('runs with truthy closure condition', function () {
|
||||
expect($this->whenObject)
|
||||
->when(
|
||||
function () { return '1'; },
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeTrue();
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(2);
|
||||
});
|
||||
|
||||
it('skips with falsy closure condition', function () {
|
||||
expect($this->whenObject)
|
||||
->when(
|
||||
function () { return '0'; },
|
||||
function ($value) {
|
||||
return $value->trueValue->toBeFalse(); // fails
|
||||
}
|
||||
)
|
||||
->foo->toEqual('foo');
|
||||
|
||||
expect(static::getCount())->toBe(1);
|
||||
});
|
||||
|
||||
it('can be used in higher order tests')
|
||||
->expect(false)
|
||||
->when(
|
||||
function () { return true; },
|
||||
function ($value) {
|
||||
return $value->toBeTrue();
|
||||
}
|
||||
)
|
||||
->throws(ExpectationFailedException::class, 'false is true');
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
beforeEach()->assertTrue(true);
|
||||
|
||||
it('proxies calls to object')->assertTrue(true);
|
||||
@ -8,4 +10,37 @@ it('is capable doing multiple assertions')
|
||||
->assertTrue(true)
|
||||
->assertFalse(false);
|
||||
|
||||
it('resolves expect callables correctly')
|
||||
->expect(function () { return 'foo'; })
|
||||
->toBeString()
|
||||
->toBe('foo')
|
||||
->and('bar')
|
||||
->toBeString()
|
||||
->toBe('bar');
|
||||
|
||||
test('does not treat method names as callables')
|
||||
->expect('it')->toBeString();
|
||||
|
||||
it('can tap into the test')
|
||||
->expect('foo')->toBeString()
|
||||
->tap(function () { expect($this)->toBeInstanceOf(TestCase::class); })
|
||||
->toBe('foo')
|
||||
->and('hello world')->toBeString();
|
||||
|
||||
it('can pass datasets into the expect callables')
|
||||
->with([[1, 2, 3]])
|
||||
->expect(function (...$numbers) { return $numbers; })->toBe([1, 2, 3])
|
||||
->and(function (...$numbers) { return $numbers; })->toBe([1, 2, 3]);
|
||||
|
||||
it('can pass datasets into the tap callable')
|
||||
->with([[1, 2, 3]])
|
||||
->tap(function (...$numbers) { expect($numbers)->toBe([1, 2, 3]); });
|
||||
|
||||
it('can pass shared datasets into callables')
|
||||
->with('numbers.closure.wrapped')
|
||||
->expect(function ($value) { return $value; })
|
||||
->and(function ($value) { return $value; })
|
||||
->tap(function ($value) { expect($value)->toBeInt(); })
|
||||
->toBeInt();
|
||||
|
||||
afterEach()->assertTrue(true);
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
beforeEach(function () {
|
||||
$this->shouldSkip = true;
|
||||
});
|
||||
|
||||
it('do not skips')
|
||||
->skip(false)
|
||||
->assertTrue(true);
|
||||
@ -31,3 +35,12 @@ it('skips with condition and message')
|
||||
it('skips when skip after assertion')
|
||||
->assertTrue(true)
|
||||
->skip();
|
||||
|
||||
it('can use something in the test case as a condition')
|
||||
->skip(function () { return $this->shouldSkip; }, 'This test was skipped')
|
||||
->assertTrue(false);
|
||||
|
||||
it('can user higher order callables and skip')
|
||||
->skip(function () { return $this->shouldSkip; })
|
||||
->expect(function () { return $this->shouldSkip; })
|
||||
->toBeFalse();
|
||||
|
||||
@ -2,26 +2,50 @@
|
||||
|
||||
global $globalHook;
|
||||
|
||||
// NOTE: this test does not have a $globalHook->calls offset since it is first
|
||||
// in the directory and thus will always run before the others. See also the
|
||||
// BeforeAllTest.php for details.
|
||||
|
||||
uses()->afterAll(function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('afterAll')
|
||||
->and($globalHook->afterAll)
|
||||
->toBe(0);
|
||||
->toBe(0)
|
||||
->and($globalHook->calls)
|
||||
->afterAll
|
||||
->toBe(1);
|
||||
|
||||
$globalHook->afterAll = 1;
|
||||
$globalHook->calls->afterAll++;
|
||||
});
|
||||
|
||||
afterAll(function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('afterAll')
|
||||
->and($globalHook->afterAll)
|
||||
->toBe(1);
|
||||
->toBe(1)
|
||||
->and($globalHook->calls)
|
||||
->afterAll
|
||||
->toBe(2);
|
||||
|
||||
$globalHook->afterAll = 2;
|
||||
$globalHook->calls->afterAll++;
|
||||
});
|
||||
|
||||
test('global afterAll execution order', function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->not()
|
||||
->toHaveProperty('afterAll');
|
||||
->toHaveProperty('afterAll')
|
||||
->and($globalHook->calls)
|
||||
->afterAll
|
||||
->toBe(0);
|
||||
});
|
||||
|
||||
it('only gets called once per file', function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->not()
|
||||
->toHaveProperty('afterAll')
|
||||
->and($globalHook->calls)
|
||||
->afterAll
|
||||
->toBe(0);
|
||||
});
|
||||
|
||||
@ -1,28 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Pest\Support\Str;
|
||||
|
||||
global $globalHook;
|
||||
|
||||
uses()->beforeAll(function () use ($globalHook) {
|
||||
// HACK: we have to determine our $globalHook->calls baseline. This is because
|
||||
// two other tests are executed before this one due to filename ordering.
|
||||
$args = $_SERVER['argv'] ?? [];
|
||||
$single = (isset($args[1]) && Str::endsWith(__FILE__, $args[1])) || ($_SERVER['PEST_PARALLEL'] ?? false);
|
||||
$offset = $single ? 0 : 2;
|
||||
|
||||
uses()->beforeAll(function () use ($globalHook, $offset) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('beforeAll')
|
||||
->and($globalHook->beforeAll)
|
||||
->toBe(0);
|
||||
->toBe(0)
|
||||
->and($globalHook->calls)
|
||||
->beforeAll
|
||||
->toBe(1 + $offset);
|
||||
|
||||
$globalHook->beforeAll = 1;
|
||||
$globalHook->calls->beforeAll++;
|
||||
});
|
||||
|
||||
beforeAll(function () use ($globalHook) {
|
||||
beforeAll(function () use ($globalHook, $offset) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('beforeAll')
|
||||
->and($globalHook->beforeAll)
|
||||
->toBe(1);
|
||||
->toBe(1)
|
||||
->and($globalHook->calls)
|
||||
->beforeAll
|
||||
->toBe(2 + $offset);
|
||||
|
||||
$globalHook->beforeAll = 2;
|
||||
$globalHook->calls->beforeAll++;
|
||||
});
|
||||
|
||||
test('global beforeAll execution order', function () use ($globalHook) {
|
||||
test('global beforeAll execution order', function () use ($globalHook, $offset) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('beforeAll')
|
||||
->and($globalHook->beforeAll)
|
||||
->toBe(2);
|
||||
->toBe(2)
|
||||
->and($globalHook->calls)
|
||||
->beforeAll
|
||||
->toBe(3 + $offset);
|
||||
});
|
||||
|
||||
it('only gets called once per file', function () use ($globalHook, $offset) {
|
||||
expect($globalHook)
|
||||
->beforeAll
|
||||
->toBe(2)
|
||||
->and($globalHook->calls)
|
||||
->beforeAll
|
||||
->toBe(3 + $offset);
|
||||
});
|
||||
|
||||
@ -4,9 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\CustomTestCase;
|
||||
|
||||
use function PHPUnit\Framework\assertTrue;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function PHPUnit\Framework\assertTrue;
|
||||
|
||||
class CustomTestCase extends TestCase
|
||||
{
|
||||
public function assertCustomTrue()
|
||||
|
||||
@ -4,9 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\CustomTestCase;
|
||||
|
||||
use function PHPUnit\Framework\assertTrue;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use function PHPUnit\Framework\assertTrue;
|
||||
|
||||
class ExecutedTest extends TestCase
|
||||
{
|
||||
public static $executed = false;
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\SubFolder\SubFolder\SubFolder;
|
||||
namespace Tests\CustomTestCaseInSubFolders\SubFolder\SubFolder;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
<?php
|
||||
|
||||
uses(Tests\SubFolder\SubFolder\SubFolder\CustomTestCaseInSubFolder::class)->in('CustomTestCaseInSubFolders/SubFolder');
|
||||
@ -1,8 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Tests\CustomTestCaseInSubFolders\SubFolder\SubFolder\CustomTestCaseInSubFolder;
|
||||
|
||||
uses(CustomTestCaseInSubFolder::class)->in('PHPUnit/CustomTestCaseInSubFolders/SubFolder/SubFolder');
|
||||
|
||||
uses()->group('integration')->in('Visual');
|
||||
|
||||
$globalHook = (object) []; // NOTE: global test value container to be mutated and checked across files, as needed
|
||||
// NOTE: global test value container to be mutated and checked across files, as needed
|
||||
$globalHook = (object) ['calls' => (object) ['beforeAll' => 0, 'afterAll' => 0]];
|
||||
|
||||
uses()
|
||||
->beforeEach(function () {
|
||||
@ -10,11 +15,13 @@ uses()
|
||||
})
|
||||
->beforeAll(function () use ($globalHook) {
|
||||
$globalHook->beforeAll = 0;
|
||||
$globalHook->calls->beforeAll++;
|
||||
})
|
||||
->afterEach(function () {
|
||||
$this->ith = 0;
|
||||
})
|
||||
->afterAll(function () use ($globalHook) {
|
||||
$globalHook->afterAll = 0;
|
||||
$globalHook->calls->afterAll++;
|
||||
})
|
||||
->in('Hooks');
|
||||
|
||||
23
tests/Unit/Plugins/Environment.php
Normal file
23
tests/Unit/Plugins/Environment.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Pest\Plugins\Environment;
|
||||
|
||||
test('environment is set to CI when --ci option is used', function () {
|
||||
$previousName = Environment::name();
|
||||
|
||||
$plugin = new Environment();
|
||||
|
||||
$plugin->handleArguments(['foo', '--ci', 'bar']);
|
||||
|
||||
expect(Environment::name())->toBe(Environment::CI);
|
||||
|
||||
Environment::name($previousName);
|
||||
});
|
||||
|
||||
test('environment is set to Local when --ci option is not used', function () {
|
||||
$plugin = new Environment();
|
||||
|
||||
$plugin->handleArguments(['foo', 'bar', 'baz']);
|
||||
|
||||
expect(Environment::name())->toBe(Environment::LOCAL);
|
||||
});
|
||||
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Pest\Plugins\Version;
|
||||
use function Pest\version;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
|
||||
use function Pest\version;
|
||||
|
||||
it('outputs the version when --version is used', function () {
|
||||
$output = new BufferedOutput();
|
||||
$plugin = new Version($output);
|
||||
|
||||
@ -1,13 +1,74 @@
|
||||
<?php
|
||||
|
||||
use Pest\Exceptions\DatasetMissing;
|
||||
use Pest\Exceptions\TestAlreadyExist;
|
||||
use Pest\Factories\TestCaseFactory;
|
||||
use Pest\Plugins\Environment;
|
||||
use Pest\TestSuite;
|
||||
|
||||
it('does not allow to add the same test description twice', function () {
|
||||
$testSuite = new TestSuite(getcwd(), 'tests');
|
||||
$test = function () {};
|
||||
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
|
||||
$this->expectException(TestAlreadyExist::class);
|
||||
$this->expectExceptionMessage(sprintf('A test with the description `%s` already exist in the filename `%s`.', 'foo', __FILE__));
|
||||
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
|
||||
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
|
||||
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
|
||||
})->throws(
|
||||
TestAlreadyExist::class,
|
||||
sprintf('A test with the description `%s` already exist in the filename `%s`.', 'foo', __FILE__),
|
||||
);
|
||||
|
||||
it('alerts users about tests with arguments but no input', function () {
|
||||
$testSuite = new TestSuite(getcwd(), 'tests');
|
||||
$test = function (int $arg) {};
|
||||
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
|
||||
})->throws(
|
||||
DatasetMissing::class,
|
||||
sprintf("A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", 'foo', 1, 'int $arg', __FILE__),
|
||||
);
|
||||
|
||||
it('can return an array of all test suite filenames', function () {
|
||||
$testSuite = TestSuite::getInstance(getcwd(), 'tests');
|
||||
$test = function () {};
|
||||
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'foo', $test));
|
||||
$testSuite->tests->set(new TestCaseFactory(__FILE__, 'bar', $test));
|
||||
|
||||
expect($testSuite->tests->getFilenames())->toEqual([
|
||||
__FILE__,
|
||||
__FILE__,
|
||||
]);
|
||||
});
|
||||
|
||||
it('can filter the test suite filenames to those with the only method', function () {
|
||||
$testSuite = new TestSuite(getcwd(), 'tests');
|
||||
$test = function () {};
|
||||
|
||||
$testWithOnly = new TestCaseFactory(__FILE__, 'foo', $test);
|
||||
$testWithOnly->only = true;
|
||||
$testSuite->tests->set($testWithOnly);
|
||||
|
||||
$testSuite->tests->set(new TestCaseFactory('Baz/Bar/Boo.php', 'bar', $test));
|
||||
|
||||
expect($testSuite->tests->getFilenames())->toEqual([
|
||||
__FILE__,
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not filter the test suite filenames to those with the only method when working in CI pipeline', function () {
|
||||
$previousEnvironment = Environment::name();
|
||||
Environment::name(Environment::CI);
|
||||
$testSuite = TestSuite::getInstance(getcwd(), 'tests');
|
||||
|
||||
$test = function () {};
|
||||
|
||||
$testWithOnly = new TestCaseFactory(__FILE__, 'foo', $test);
|
||||
$testWithOnly->only = true;
|
||||
$testSuite->tests->set($testWithOnly);
|
||||
|
||||
$testSuite->tests->set(new TestCaseFactory('Baz/Bar/Boo.php', 'bar', $test));
|
||||
|
||||
expect($testSuite->tests->getFilenames())->toEqual([
|
||||
__FILE__,
|
||||
'Baz/Bar/Boo.php',
|
||||
]);
|
||||
|
||||
Environment::name($previousEnvironment);
|
||||
});
|
||||
|
||||
29
tests/Visual/JUnit.php
Normal file
29
tests/Visual/JUnit.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Pest\Logging\JUnit;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Framework\Warning;
|
||||
|
||||
beforeEach(function () {
|
||||
file_put_contents(__DIR__ . '/junit.html', '');
|
||||
});
|
||||
|
||||
it('is can successfully call all public methods', function () {
|
||||
$junit = new JUnit(__DIR__ . '/junit.html');
|
||||
$junit->startTestSuite(new TestSuite());
|
||||
$junit->startTest($this);
|
||||
$junit->addError($this, new Exception(), 0);
|
||||
$junit->addFailure($this, new AssertionFailedError(), 0);
|
||||
$junit->addWarning($this, new Warning(), 0);
|
||||
$junit->addIncompleteTest($this, new Exception(), 0);
|
||||
$junit->addRiskyTest($this, new Exception(), 0);
|
||||
$junit->addSkippedTest($this, new Exception(), 0);
|
||||
$junit->endTest($this, 0);
|
||||
$junit->endTestSuite(new TestSuite());
|
||||
$this->expectNotToPerformAssertions();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
unlink(__DIR__ . '/junit.html');
|
||||
});
|
||||
@ -9,7 +9,7 @@ test('visual snapshot of test suite on success', function () {
|
||||
]);
|
||||
|
||||
$output = function () use ($testsPath) {
|
||||
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest'], dirname($testsPath), ['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false]));
|
||||
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest'], dirname($testsPath), ['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0]));
|
||||
|
||||
$process->run();
|
||||
|
||||
|
||||
32
tests/Visual/TeamCity.php
Normal file
32
tests/Visual/TeamCity.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Pest\Logging\TeamCity;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use PHPUnit\Framework\TestResult;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Framework\Warning;
|
||||
use PHPUnit\TextUI\DefaultResultPrinter;
|
||||
|
||||
beforeEach(function () {
|
||||
file_put_contents(__DIR__ . '/output.txt', '');
|
||||
});
|
||||
|
||||
it('is can successfully call all public methods', function () {
|
||||
$teamCity = new TeamCity(__DIR__ . '/output.txt', false, DefaultResultPrinter::COLOR_ALWAYS);
|
||||
expect($teamCity::isPestTest($this))->toBeTrue();
|
||||
$teamCity->startTestSuite(new TestSuite());
|
||||
$teamCity->startTest($this);
|
||||
$teamCity->addError($this, new Exception('Don\'t worry about this error. Its purposeful.'), 0);
|
||||
$teamCity->addFailure($this, new AssertionFailedError('Don\'t worry about this error. Its purposeful.'), 0);
|
||||
$teamCity->addWarning($this, new Warning(), 0);
|
||||
$teamCity->addIncompleteTest($this, new Exception(), 0);
|
||||
$teamCity->addRiskyTest($this, new Exception(), 0);
|
||||
$teamCity->addSkippedTest($this, new Exception(), 0);
|
||||
$teamCity->endTest($this, 0);
|
||||
$teamCity->printResult(new TestResult());
|
||||
$teamCity->endTestSuite(new TestSuite());
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
unlink(__DIR__ . '/output.txt');
|
||||
});
|
||||
0
tests/Visual/junit.html
Normal file
0
tests/Visual/junit.html
Normal file
Reference in New Issue
Block a user