Compare commits

...

729 Commits

Author SHA1 Message Date
7a699e16db release: v2.2.0 2023-03-22 10:05:58 +00:00
341ba56bb9 fix: uses DateTimeInterface instead 2023-03-22 09:46:23 +00:00
a320cc3e2b Merge pull request #721 from faissaloux/fix-dirty-files-filter
[2.x] Fix `--dirty` not working on Windows
2023-03-22 09:43:13 +00:00
8b428357b2 chore: collision snapshots 2023-03-22 00:01:11 +00:00
bb6d6b0951 tests: adds collision tests 2023-03-21 23:55:33 +00:00
b94b8c6a4f fix dirty files filter 2023-03-21 23:48:22 +00:00
43894afa18 chore: updates snapshots 2023-03-21 21:10:58 +00:00
28de31a8b9 Merge pull request #698 from fabio-ivona/dataset-arguments-check
[chore] Better dataset arguments mismatch message
2023-03-21 21:10:30 +00:00
974e70d7d1 Merge branch '2.x' into dataset-arguments-check 2023-03-21 21:10:22 +00:00
f914f1ad87 fix: adds --parallel option to help 2023-03-21 21:07:57 +00:00
14dd5cb57b fix: test result on parallel 2023-03-21 21:05:11 +00:00
077ed287b7 chore: updates snapshots 2023-03-21 20:33:39 +00:00
9a41f2ff82 Merge pull request #716 from dansysanalyst/improve_types
Improve types in Greater/Lesser Expectations
2023-03-21 20:30:34 +00:00
88f29e4180 Merge branch '2.x' into improve_types 2023-03-21 20:30:29 +00:00
c34f649724 release: v2.1.0 2023-03-21 17:04:03 +00:00
e1e4f8d884 chore: rebuilds snapshots 2023-03-21 16:57:07 +00:00
2d6d8b810b Merge pull request #715 from pestphp/fix/method-names
[2.x] Fix `ふが` type of chars
2023-03-21 16:55:57 +00:00
bcd1503cad feat: "only" method 2023-03-21 16:54:37 +00:00
e0f2919f62 expand Str::evaluable test cases 2023-03-21 17:39:01 +01:00
a8bd353ba6 Including tests for Date and DateTimeImmutable 2023-03-21 15:24:42 +01:00
ed3bb2634d using php documentation regex 2023-03-21 15:15:38 +01:00
48ae4bfc18 fix: description when using ふが chars 2023-03-21 13:45:20 +00:00
26bb0b6eec chore: bumps dependencies 2023-03-21 13:44:18 +00:00
236a9bd7ce chore: style changes 2023-03-21 13:42:21 +00:00
f4d19c90d3 chore: bumps dependencies 2023-03-21 13:42:10 +00:00
ecbaff503e Add Date/DateTimeImmutable to Expectations 2023-03-21 14:37:17 +01:00
9d0cd32e3f release: v2.0.2 2023-03-20 17:52:35 +00:00
8782e9c34e release: v2.0.2 2023-03-20 17:52:15 +00:00
a4932e41de release: v2.0.2 2023-03-20 17:51:54 +00:00
522ac55d5f chore: rebuilds snapshots 2023-03-20 17:45:35 +00:00
b3a8aef6ac chore: improves tests 2023-03-20 17:42:44 +00:00
8068bebebd chore: updates snapshots 2023-03-20 17:36:18 +00:00
b887116e5c fix: Pest.php file not loaded in certain environments 2023-03-20 17:34:42 +00:00
6071d86ac6 tests: update snapshot testing 2023-03-20 16:14:40 +00:00
5723da1043 tests: updates snapshot testing 2023-03-20 16:10:53 +00:00
17e242a5f6 tests: windows global functions 2023-03-20 16:08:14 +00:00
c9a8007811 chore: uses instance method of facade 2023-03-20 16:00:11 +00:00
c64c41a4d9 chore: requires "phpunit/phpunit": "^10.0.17" 2023-03-20 15:52:41 +00:00
da4bf7f5c3 update snapshots 2023-03-20 16:49:15 +01:00
bb5dbc878e chore: bumps required version of arch plugin 2023-03-20 15:22:10 +00:00
e3ab27e2ec release: v2.0.1 2023-03-20 11:02:54 +00:00
8f91f40e8e fix: removes version from composer.json 2023-03-20 10:55:47 +00:00
2973b600f5 release: v2.0.0 2023-03-20 10:39:49 +00:00
37d4434000 release: v2.0.0 2023-03-20 10:36:51 +00:00
627b673380 docs: updates release 2023-03-20 10:35:27 +00:00
bc1b11054c docs: updates release 2023-03-20 10:35:10 +00:00
8350d74020 chore: uses stable minimum-stability 2023-03-20 10:28:10 +00:00
1431a2a897 chore: bumps development tools 2023-03-20 00:21:29 +00:00
2906a2de2d Merge pull request #702 from dansysanalyst/grammar_fix
Exceptions - Grammar Fix
2023-03-19 23:44:32 +00:00
540c2a56bd grammar fixes 2023-03-20 00:37:32 +01:00
2fb8690320 docs: updates changelog 2023-03-19 23:15:14 +00:00
88e047bd27 tests: for test names starting with P 2023-03-19 18:34:20 +00:00
8f630c064f Merge pull request #700 from faissaloux/fix-test-case-name-starts-with-P
Fix test Case Name starts with a P
2023-03-19 17:55:38 +00:00
54fd188299 fix test Case Name starts with a P 2023-03-19 17:46:49 +00:00
4db2318a66 chore: specifies stable version of Collision 2023-03-19 17:09:49 +00:00
7e1c769d1c Delete nightly-tests.yml 2023-03-19 16:45:49 +00:00
7a57f9f9b8 chore: incrementally supports PHPUnit patch versions 2023-03-19 16:45:43 +00:00
961e0aec66 feat: removes non used subscriber 2023-03-19 15:40:53 +00:00
ccfcd336fe fix: profile plugin 2023-03-19 04:00:10 +00:00
7287d65865 chore: runs nightly builds against 10.0.x-dev 2023-03-19 03:58:04 +00:00
d96ddaeaac feat: clarifies that profile is not supported in parallel 2023-03-19 03:51:27 +00:00
085d3436c8 fix: detection of dirty files 2023-03-19 03:38:20 +00:00
2da899a2b1 fix: --todos in parallel and feedback on process isolation 2023-03-19 02:01:08 +00:00
48ea48981b fix: todos flag 2023-03-19 00:49:06 +00:00
084f7c596f feat: improves --bail 2023-03-19 00:41:41 +00:00
aafdf6f39c chore: improves static checking 2023-03-18 23:48:01 +00:00
d4c66d73a0 chore: static checks 2023-03-18 23:35:05 +00:00
7d89d3546e update snapshots 2023-03-19 00:22:31 +01:00
840364891c fix: prepares for nightly phpunit release 2023-03-18 23:19:20 +00:00
1f3e5115c7 fix failure message 2023-03-19 00:14:18 +01:00
9de85175db better dataset arguments mismatch message 2023-03-18 23:58:09 +01:00
0bf051610d Merge pull request #694 from faissaloux/refactor-result-php
Replace magic number `0` by meaningful const
2023-03-18 20:08:48 +00:00
42fc80b76d Merge pull request #695 from faissaloux/remove-unused-imports
Remove unused imports
2023-03-18 20:08:28 +00:00
ff1249b5cf remove unused imports 2023-03-18 16:16:45 +01:00
331585a0ba replace magic number 0 by meaningful const 2023-03-18 16:03:43 +01:00
0c808736b8 chore: removes changelog 2023-03-18 02:10:40 +00:00
9f0dc108fa Update FUNDING.yml 2023-03-18 02:06:57 +00:00
cf5c6f9ffd chore: adjusts ci 2023-03-18 00:59:05 +00:00
f6d6a4db78 chore: adjusts ci 2023-03-18 00:58:44 +00:00
ef3305ec23 docs: updates readme 2023-03-18 00:41:49 +00:00
f6ce6cece7 docs: updates links 2023-03-18 00:31:36 +00:00
32923f32a8 docs: updates links 2023-03-18 00:30:39 +00:00
02f3202a61 docs: updates link 2023-03-17 23:32:49 +00:00
e6af0c8a77 docs: update readme 2023-03-17 23:18:45 +00:00
52a8183fcb docs: updates discord 2023-03-17 23:11:05 +00:00
c2f57811e1 chore: updates readme 2023-03-17 23:08:06 +00:00
639029349d chore: skips parallel on windows 2023-03-17 22:49:25 +00:00
595172aba6 docs: updates sponsors 2023-03-17 22:18:21 +00:00
e7af5a4cf9 fix: deprecations 2023-03-17 20:39:29 +00:00
ef7d1527da Fixes unit stub 2023-03-17 17:02:20 +00:00
2a370a2a76 fix: support message 2023-03-17 15:22:55 +00:00
b828843974 fix: spacing in init 2023-03-17 15:19:22 +00:00
0b99c72937 fix: tries getenv 2023-03-17 11:50:41 +00:00
ec4f15132e feat: allows to skip support via env 2023-03-17 11:42:23 +00:00
0e4cc94471 feat: improve --init command 2023-03-17 10:58:23 +00:00
9e89fb5e23 feat: improves init plugin 2023-03-17 10:31:03 +00:00
26cf278103 feat: adds laravel stubs 2023-03-17 10:28:50 +00:00
050fe38a90 fix: default phpunit file 2023-03-17 10:25:51 +00:00
f07c3a2480 tests: clarifies dependencies 2023-03-17 02:06:26 +00:00
ae365324a8 tests: adds arch tests 2023-03-17 01:45:40 +00:00
b4b212a426 feat: moves "UsedOn" to "UsedIn" 2023-03-16 19:48:52 +00:00
f52d4392a6 Merge pull request #691 from localheinz/feature/phpunit
Enhancement: Run tests against `phpunit/phpunit:dev-main` every 12 hours
2023-03-16 12:09:47 +00:00
c662c59daf chore: updates build name 2023-03-16 12:09:35 +00:00
ef4a487322 feat: adds --bail to help 2023-03-16 11:57:50 +00:00
30b151f927 feat: --bail 2023-03-16 11:55:42 +00:00
b93bf82be6 Merge pull request #692 from xiCO2k/fix/help-command
[2.x] Help Command
2023-03-16 11:43:51 +00:00
ea3827fe7f fix: Help Command 2023-03-16 11:42:29 +00:00
7cd6b4ff40 wip 2023-03-16 11:06:07 +00:00
544af117bb Enhancement: Run tests against phpunit/phpunit:dev-main every 12 hours 2023-03-16 10:37:11 +01:00
d71af91360 feat: re-adds tap to avoid BC 2023-03-14 19:09:46 +00:00
fccb90c8ea fix: coverage lines 2023-03-14 14:26:08 +00:00
4b1cad2493 fix: description when before each fails 2023-03-14 13:06:42 +00:00
57cd294f0f chore: updates snapshots 2023-03-14 01:28:29 +00:00
837552b11d feat: improves dumper 2023-03-13 21:36:51 +00:00
966c382d1a Merge pull request #685 from fabio-ivona/fix-multiple-dataset-test-case-access
Fix multiple dataset test case access
2023-03-13 17:53:40 +00:00
43e17741eb update snapshots 2023-03-13 18:49:35 +01:00
d6e3906da6 Fix unreachable test case from multiple datasets 2023-03-13 18:14:26 +01:00
dbef162fa6 failing test 2023-03-13 18:13:57 +01:00
1bd9c9e60d wip 2023-03-13 18:13:57 +01:00
d1adc65037 chore: bumps collision 2023-03-13 17:10:17 +00:00
ae15fa668d fix: type checking 2023-03-13 16:55:41 +00:00
88e576c3a3 Merge pull request #684 from fabio-ivona/fix-phpunit-changes
Fix PHPUnit internal breaking changes
2023-03-13 16:26:57 +00:00
60950c624f fix 2023-03-13 17:26:27 +01:00
3d1e0c3f9f bump to phpunit 10.0.16 2023-03-13 17:11:01 +01:00
d6c8149a3a fix phpunit 10.0.16 internal breaking changes 2023-03-13 14:20:42 +01:00
aedf2e3727 Merge branch 'pestphp:2.x' into 2.x 2023-03-13 14:04:33 +01:00
6316828fbd feat: adds brianium/paratest and pestphp/pest-plugin-arch by default 2023-03-12 15:51:02 +00:00
24a9ab6761 Merge branch 'pestphp:2.x' into 2.x 2023-03-12 14:00:36 +01:00
0a76032b1b chore: bumps phpunit 2023-03-09 23:48:46 +00:00
730bc7d3b3 chore: code style 2023-03-05 01:48:07 +00:00
99c7bb705e Merge pull request #676 from alirezasalehizadeh/shorter_if
[2.x] shorter if condition
2023-03-03 21:19:32 +00:00
fc747c306d [2.x] shorter if condition 2023-03-03 23:27:25 +03:30
b6b8a72a6a docs: removes sponsor 2023-03-03 10:23:56 +00:00
36f005981b chore: uses stable version of collision 2023-03-03 10:01:06 +00:00
eadc6f4307 chore: fix tests 2023-03-03 09:50:41 +00:00
2876ac590d fix: todo in parallel 2023-03-03 01:09:35 +00:00
99436f94f9 chore: uses unstable version of collision 2023-03-03 00:54:32 +00:00
60f82a21db fix: buffer in paralllel combined with coverage 2023-03-02 23:04:21 +00:00
f68e6cefd4 chore: type checking 2023-03-02 22:58:11 +00:00
b939d94cda fix: non needded output 2023-03-02 22:56:09 +00:00
37d71adb4e fix: cache directory 2023-03-02 22:18:03 +00:00
813f63360b feat: extends test outcome to state generator 2023-03-02 21:57:46 +00:00
ad97b202c4 fix: converter 2023-03-02 21:24:05 +00:00
1e61034e86 feat: support for deprecated, notices, and warnings 2023-03-02 20:37:18 +00:00
3f6b2e856e tests: helpers fix 2023-03-02 17:51:34 +00:00
3df68b288a tests: deprecations 2023-03-02 17:51:21 +00:00
a75e899d98 chore: unsets collision on testing 2023-03-02 17:51:10 +00:00
f25a9f5558 feat: warnings support 2023-03-02 17:50:57 +00:00
69a0c3ba99 chore: dont fails on warnings 2023-03-02 17:50:47 +00:00
e1bb1d8c2d fix: printer parallel not displaying progress of deprecations 2023-03-02 17:50:36 +00:00
62238b2714 fix: don't registers exception handlers 2023-03-02 16:24:16 +00:00
e9f83dc020 fix: discovering phpunit tests on parallel 2023-03-02 14:09:18 +00:00
b15dc03d16 chore: bumps dependencies 2023-03-02 14:09:05 +00:00
3af60b874e chore: upgrades deps 2023-02-28 20:17:49 +05:30
1da1eeb384 Merge branch 'pestphp:2.x' into 2.x 2023-02-26 10:02:57 +01:00
8abad572ec chore: fix CI testing 2023-02-25 17:45:43 +05:30
20012b65fb chore: bumps phpunit 2023-02-25 17:42:29 +05:30
244087db27 fix: parallel status code 2023-02-25 17:42:24 +05:30
b54c24a589 fix: --parallel exit code 2023-02-25 17:33:28 +05:30
c664094f35 fix: --retry option update 2023-02-21 22:04:30 +00:00
f5d71b9282 Merge pull request #669 from fabio-ivona/init-tweak
[chore] tweak init command
2023-02-21 00:03:26 +01:00
a478798cfe lint 2023-02-21 00:02:15 +01:00
a6e133a194 add --ansi option to artisan command call 2023-02-20 23:56:40 +01:00
46b785f29f Update TestCase.php 2023-02-20 22:54:24 +00:00
69b1c08558 refactor 2023-02-20 23:44:15 +01:00
2a1db41880 fix: cache being mixed with phpunit 2023-02-20 22:38:03 +00:00
64dbcf0a26 init command tweak 2023-02-20 23:17:41 +01:00
683910bff4 fix: improves dump message 2023-02-20 18:10:51 +00:00
7492b331a0 chore: fixes scripts 2023-02-20 17:52:26 +00:00
ae8df3f51d chore: runs tests in Parallel on CI 2023-02-20 17:41:43 +00:00
658c428b71 chore: bumps dependencies 2023-02-20 17:33:13 +00:00
d7ecef80e9 chore: improves type checking 2023-02-20 17:33:03 +00:00
09644640bf feat: improves error handling 2023-02-20 17:28:33 +00:00
ba1f2df40b chore: bumps dependencies 2023-02-20 17:23:57 +00:00
0348037638 fix: retry option should not be used in parallel 2023-02-20 17:23:57 +00:00
23b42730ba Merge pull request #668 from fabio-ivona/custom-expectations-fix
Store interceptors for use with Collision
2023-02-19 17:00:02 +00:00
9ded563e4b store interceptors for use with Collision
this is a workaround for php bug https://github.com/php/php-src/issues/10623, that prevents Collision to retrieve info about the interceptor handler closure. (see https://github.com/nunomaduro/collision/pull/255)
2023-02-19 11:07:44 +01:00
8a48a3da7f chore: updates snapshots 2023-02-18 16:50:12 +00:00
61a35d65c4 chore: upgrades dependencies 2023-02-18 16:47:32 +00:00
aafbd04753 style: removes unused variable 2023-02-18 16:46:34 +00:00
56a2f1b733 fix: what's considered a "deffect" test 2023-02-18 16:09:50 +00:00
aa3369757c feat: improves dump on internal error 2023-02-18 16:09:25 +00:00
f309e06292 fix: assertions on opposite expectations 2023-02-18 15:27:25 +00:00
05989c35a7 chore: increases necessary version of paratest 2023-02-18 15:21:59 +00:00
813a74759b feat: improves parallel output 2023-02-18 15:04:10 +00:00
c9fb8e6f52 feat: improves parallel output 2023-02-18 15:00:59 +00:00
d374a46c4d feat: improves parallel output 2023-02-18 15:00:49 +00:00
0ce70a0180 fix: relaxes if ob was not started 2023-02-18 14:47:27 +00:00
7fc12613a8 feat: kernel dump 2023-02-18 14:39:47 +00:00
d0e949bf19 chore: dont export non necessary files on vendor 2023-02-18 14:39:47 +00:00
957dabc3f0 feat: improves help table 2023-02-18 14:39:47 +00:00
b4b4bf3685 style: plugin actions improvements 2023-02-18 14:39:47 +00:00
49619ff2b5 feat: improves DX on auto-complete 2023-02-18 14:39:47 +00:00
efca71f1e7 fix: test suite loader 2023-02-18 14:39:47 +00:00
86fdfb75cf chore: bumps dependencies 2023-02-18 14:39:47 +00:00
f865e93050 chore: spacing in changelog 2023-02-18 14:39:47 +00:00
4fccddebb0 style: removes non important note 2023-02-18 14:39:47 +00:00
15ef5f12b8 Update README.md 2023-02-17 16:25:36 +00:00
c87aabf5d4 feat: prefixes evaluable 2023-02-14 08:57:04 +00:00
0d9c11c99a feat: prefixes evaluables 2023-02-14 08:56:56 +00:00
7fdc7d6997 feat: improves auto-completion 2023-02-14 08:56:46 +00:00
7fe8399d48 style: renames classe name 2023-02-14 08:56:38 +00:00
9be89c4042 fix: removes unused param 2023-02-14 08:56:18 +00:00
a61db76c24 feat: deprecates only feature 2023-02-14 08:40:02 +00:00
ea6af719e0 reverts: change on tests repository 2023-02-14 08:32:47 +00:00
9f5506364b feat: improves should not happen exception 2023-02-14 08:29:58 +00:00
2d70f18a93 docs: includes license in overrides 2023-02-14 08:01:15 +00:00
427ee89ae6 feat: improves exporter 2023-02-13 23:56:19 +00:00
dd4247e150 Merge pull request #647 from cerbero90/feature/compact-dataset-description
[2.x] Compact dataset descriptions
2023-02-13 23:32:36 +00:00
cac777bce1 Merge branch '2.x' into feature/compact-dataset-description 2023-02-13 23:32:02 +00:00
3c0390b1ac Merge pull request #656 from fabio-ivona/scheduled-workflows
[workflows] add scheduled runs
2023-02-13 23:26:12 +00:00
2df49947df chore: bumps dependencies 2023-02-13 23:25:35 +00:00
26369a19b4 chore: coding style 2023-02-13 23:25:27 +00:00
2b3e146d5a Merge pull request #658 from WendellAdriel/makefile-add
Add Makefile to make it easier for developers to run docker commands
2023-02-13 19:34:29 +00:00
aff8e33eca Merge pull request #662 from pestphp/2.x_parallel_cleanup
2.x Parallel todo support
2023-02-13 13:24:54 +00:00
104db3f6a5 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 13:13:23 +00:00
ec8fb202b3 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 13:11:27 +00:00
e22f6e1e4d chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 12:25:19 +00:00
d57437ff02 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 12:25:00 +00:00
eb64113a3d chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 12:20:08 +00:00
3b5f9ec59d chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 12:18:59 +00:00
666b2f3fd0 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 12:13:53 +00:00
2bc33c7cd5 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 11:22:42 +00:00
8ee7a4deef chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 11:19:18 +00:00
69afb31bb9 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 11:13:17 +00:00
5ca4c5bca9 chore(cleanup): Tidy-up and tweaks of Pest Parallel integration. 2023-02-13 09:43:49 +00:00
b6fb81e506 refactor: parallel 2023-02-13 03:08:36 +00:00
735f131222 refacto: bootstrappers and parallel minor stuff 2023-02-12 23:40:05 +00:00
8a58e984fe fix: gitignore 2023-02-12 01:20:37 +00:00
0e9c1bc0f7 fix: no --dirty tests found 2023-02-12 01:06:02 +00:00
e749af6d91 chore: updates dependencies and snapshots 2023-02-11 18:25:37 +00:00
052b9e051b fix: --retry with parallel 2023-02-11 17:39:46 +00:00
6ddc5c8572 Merge pull request #659 from pestphp/pest-parallel
Parallel support
2023-02-11 16:52:11 +00:00
8eaf4859ff chore: different refactors 2023-02-11 16:51:29 +00:00
e1406554fc WIP 2023-02-11 16:51:08 +00:00
504fd04705 WIP 2023-02-11 16:51:08 +00:00
7fe7a01d43 WIP 2023-02-11 16:51:08 +00:00
b7ec3c59b8 WIP 2023-02-11 16:51:08 +00:00
757a98230e WIP 2023-02-11 16:51:00 +00:00
c319a8e84c WIP 2023-02-11 16:51:00 +00:00
8d33c9dc89 WIP 2023-02-11 16:51:00 +00:00
2ae06a0e2d WIP 2023-02-11 16:50:59 +00:00
f107fdfa08 WIP 2023-02-11 16:50:59 +00:00
2561d47bb5 WIP 2023-02-11 16:50:59 +00:00
aff11486b2 Fixes --dirty integration 2023-02-11 16:50:59 +00:00
17cda168e1 WIP 2023-02-11 16:50:59 +00:00
d5495a7e3a WIP 2023-02-11 16:50:59 +00:00
87ee5ef36b Style 2023-02-11 16:50:59 +00:00
a34001faf0 WIP 2023-02-11 16:50:59 +00:00
dd840f8861 WIP 2023-02-11 16:50:59 +00:00
f94ea9ba0d WIP 2023-02-11 16:50:59 +00:00
6338d762fa wip 2023-02-11 16:50:59 +00:00
f48ae48677 Fixes test name 2023-02-11 16:50:59 +00:00
1658176fe1 Uses default gray 2023-02-11 16:50:59 +00:00
2f519261f5 Fixes and improvements. 2023-02-11 16:50:59 +00:00
7466667c08 WIP 2023-02-11 16:50:59 +00:00
d03302db7b WIP 2023-02-11 16:50:59 +00:00
951b54e7cd Uses ResultPrinter 2023-02-11 16:50:59 +00:00
2929af4715 WIP 2023-02-11 16:50:59 +00:00
48309931ef WIP 2023-02-11 16:50:59 +00:00
d69f61c8d3 refacto: --retry option 2023-02-11 16:48:06 +00:00
a8f0b96338 chore: bumps dependencies 2023-02-11 02:36:28 +00:00
c1663191ee chore: updates dependencies 2023-02-08 23:40:22 +00:00
ded14c5425 Merge pull request #660 from fabio-ivona/phpstan-fix
[2.x] Fix missing parameter to `Style→writeRecap()` method
2023-02-08 23:38:29 +00:00
80bea0b4b4 add missing parameter 2023-02-08 23:29:10 +01:00
2cdcf7f3e3 Add Makefile to make it easier for developers to run docker commands 2023-02-08 12:05:30 +00:00
8d34d0743f chore: bumps phpunit 2023-02-06 18:09:15 +00:00
fe763c1dc7 fix: make datasets static 2023-02-06 18:09:07 +00:00
8a2aeff9a0 add scheduled workflow runs 2023-02-06 09:13:07 +01:00
b1545f270f chore: updates dependencies 2023-02-04 11:58:02 +00:00
975f1bd9fa chore: uses stable version of PHPUnit v10 2023-02-03 10:47:07 +00:00
fed9776f9b chore: updates dev dependencies 2023-02-01 19:23:15 +00:00
803ba535c3 Merge pull request #652 from pestphp/feature/to-have-methods
feat: add support for `toHaveMethod` and `toHaveMethods`
2023-01-29 18:29:01 +00:00
60358461c4 feat: add support for toHaveMethod and toHaveMethods 2023-01-29 12:35:21 +00:00
bdff30b511 Merge pull request #651 from pestphp/docker_support
[2.x] Docker support for OSS contributors
2023-01-28 23:29:36 +00:00
6e3d940c8c Adds Docker to allow users to get the project up and running quickly. 2023-01-28 20:05:28 +00:00
b702e0c084 Adds Docker to allow users to get the project up and running quickly. 2023-01-28 20:03:55 +00:00
48caaed58c tests: updates snapshots 2023-01-21 01:36:44 +00:00
c45a303451 chore: removes unused code 2023-01-21 01:36:36 +00:00
2bffd6a51e feat: compact dataset descriptions 2023-01-17 21:02:47 +10:00
179e6a9db1 Merge pull request #642 from fabio-ivona/enable-windows-tests
[2.x] Fix #638 #641 windows test failing due directory separators
2023-01-17 11:01:34 +00:00
94cef989d3 enable windows tests 2023-01-16 21:36:33 +01:00
6982b02d48 feat: --todo flag 2023-01-15 20:51:27 +00:00
3a4a57a262 refacto: optional argument 2023-01-14 20:10:24 +00:00
67d26388de chore: bumps dependencies 2023-01-12 21:03:31 +00:00
a26e3946bd chore: removes ignored phpstan line 2023-01-12 20:58:33 +00:00
026d4920cf tests: rebuilds snapshots 2023-01-12 20:58:25 +00:00
d305f4dca0 fix: --dirty when no tests found 2023-01-11 21:32:15 +00:00
217fae0967 fix: dirty argument 2023-01-11 20:17:51 +00:00
349e2f45df chore: style changes 2023-01-11 20:11:36 +00:00
0675529320 Merge pull request #637 from pestphp/2.x-teamcity
[2.x] Teamcity support
2023-01-11 19:49:11 +00:00
0839c7e127 Add Initial teamcity support 2023-01-11 09:37:12 +01:00
15931e2418 feat: rewrites --dirty support 2023-01-10 22:23:52 +00:00
f6676118ac Merge pull request #619 from pestphp/dirty_integration
[2.x] Adds initial implementation of `--dirty` option.
2023-01-10 21:34:24 +00:00
78673ceeb1 Merge branch '2.x' into dirty_integration 2023-01-10 21:34:18 +00:00
e228d565af feat: allows to chain todo 2023-01-10 20:21:36 +00:00
51bcf6a2be chore: fixes tests 2023-01-10 00:57:23 +00:00
feedeab7e3 chore: ignores phpstan error 2023-01-10 00:20:49 +00:00
76d1a8ffed fix: ensures view are boot 2023-01-10 00:20:42 +00:00
5436ff8c49 chore: run tests on windows 2023-01-08 23:16:42 +00:00
e8b10fcc91 chore: run tests on macos 2023-01-08 23:14:17 +00:00
4083e6e26e change: removes junit support 2023-01-08 23:02:03 +00:00
3ecf351432 docs: remove todo 2023-01-08 22:56:09 +00:00
6595e6960b tests: adjusts snapshots based on code highlight 2023-01-06 20:09:25 +00:00
330b4f0171 fix: ensures --retry works with errored tests 2023-01-02 20:59:59 +00:00
43a7df2cc1 tests: update snapshots 2023-01-01 20:29:47 +00:00
0e98c5c5c4 style: removes non-used imports 2022-12-29 16:36:17 +00:00
39ee5b9b08 change: refactors DependOn to Use 2022-12-29 16:34:15 +00:00
227d32a1fd feat: improves --coverage output 2022-12-29 11:35:10 +00:00
b7e2cd758f fix: when retry.json does not exists 2022-12-29 09:37:45 +00:00
c9a02b964d fix: --retry not running all tests after passing 2022-12-29 09:34:53 +00:00
0fd5b2efe1 chore: adjusts return type 2022-12-29 09:31:17 +00:00
476f56b617 feat: adds toBeUsedOnNothing and toBeUsed 2022-12-28 16:27:08 +00:00
174a9ca60b feat: adds toBeUsedOn 2022-12-28 16:09:47 +00:00
406fcf72ae fix: overrides being used on regular phpunit 2022-12-28 14:21:07 +00:00
37b1367d25 chore: adjusts snapshots 2022-12-23 23:03:53 +00:00
a55953a8e0 chore: remove coverage type 2022-12-23 23:03:46 +00:00
f120b32791 Merge pull request #620 from alexmanase/2.x-fix-ignored-description-for-lazy-datasets
[2.x] Fix: ignored dataset description for string description
2022-12-23 23:01:45 +00:00
ca113127cc Merge branch '2.x' into 2.x-fix-ignored-description-for-lazy-datasets 2022-12-23 23:01:39 +00:00
138bdf599b feat: adds toOnlyBeUsedOn 2022-12-21 04:00:45 +00:00
522504916b fix: namespace import 2022-12-21 00:11:55 +00:00
b04207d9ea feat: improves not->toDependOn 2022-12-21 00:09:38 +00:00
9596274b14 fix: test suite loader duplicating tests 2022-12-20 22:42:31 +00:00
8d018ea3f1 chore: bumps termwind 2022-12-20 22:42:31 +00:00
0f0af9eefb Merge pull request #625 from SamuelMwangiW/patch-1
Tests Badge
2022-12-17 22:25:43 +01:00
8aece3981d Tests Badge
Apply changes to the badge as per https://github.com/badges/shields/issues/8671
2022-12-17 22:06:21 +03:00
1c9c408cf3 fix: style of memory plugin 2022-12-15 02:10:47 +00:00
3911cfec6d chore: updates snapshots 2022-12-14 14:56:31 +00:00
6d7fd66f82 tests: on opposite throwExpectationFailedException 2022-12-14 14:56:26 +00:00
e1e926076a fix: types on arch expectations 2022-12-14 14:56:11 +00:00
68bf8a2d26 feat: adds arch related expectations 2022-12-13 15:27:22 +00:00
c0d9f739b3 chore: fixes script name 2022-12-12 13:44:39 +00:00
d33cc19778 fix: ignored dataset description for string description 2022-12-07 20:10:23 +02:00
f940b89284 chore: upgrades dev tools 2022-12-07 14:17:44 +00:00
21990ccd8b refacto: attribute above 2022-12-07 14:17:30 +00:00
c01654efcc refactor: makes const final 2022-12-07 14:12:27 +00:00
1ad631c528 chore: ignores eval only on test factory 2022-12-07 09:32:27 +00:00
ecb5d9c83e chore: bumps certain types of type coverage 2022-12-07 09:30:50 +00:00
0039dbde38 chore: adds type coverage at 96% 2022-12-07 09:26:35 +00:00
70f447a8bc chore: improves type coverage 2022-12-07 09:17:35 +00:00
d29d68a2c2 Adds initial implementation of --dirty option. 2022-12-05 08:59:42 +00:00
c5f6923e5a fix: methods name with \ 2022-12-04 23:49:58 +00:00
34878bf432 Reverts loading Arch 2022-12-04 23:43:20 +00:00
fab08f0e20 Loads Arch files 2022-12-04 23:38:46 +00:00
850955d7dd fix: reverts autoloading functions 2022-12-04 23:07:39 +00:00
606d627f1d Revert "fix: default tests path"
This reverts commit 3bc356ceec.
2022-12-04 20:06:42 +00:00
dfe8a3deeb revert: inline testing 2022-12-04 20:06:07 +00:00
3bc356ceec fix: default tests path 2022-12-04 19:02:04 +00:00
7ad045d6b7 feat: inline testing 2022-12-04 18:38:20 +00:00
3324455e0a feat: only registers globals if necessary 2022-12-04 16:32:45 +00:00
7ffc5602b4 Merge pull request #584 from fabio-ivona/datasets-scope
[2.x] Dataset scopes
2022-11-30 22:35:37 +00:00
8944fdd96f update snapshots 2022-11-30 14:08:47 +01:00
3aaa93931a Merge branch 'master' into datasets-scope
# Conflicts:
#	tests/.snapshots/success.txt
2022-11-30 14:06:05 +01:00
d77715b0fe fix: --filter option 2022-11-21 20:47:47 +00:00
17cc194ad1 Adds sponsor 2022-11-21 12:09:32 +00:00
f9397924fa revert(fix): on risky expecatations 2022-11-18 02:28:33 +00:00
9281060ab5 fix: risky tests on oposite expectations 2022-11-18 02:24:54 +00:00
6a3cc48d6b Updates snapshots 2022-11-09 21:00:48 +00:00
7194a87d0a Merge pull request #603 from alexmanase/2.x_fix/iterator-to-array
[2.x] Fix storing of lazy datasets into internal array
2022-11-09 20:58:57 +00:00
3e325e3364 chore: improves type checking 2022-11-09 20:34:18 +00:00
348bd4b923 feat: improves feedback when test misses description 2022-11-09 20:31:35 +00:00
f6cfd425c6 tests: updates snapshots 2022-11-09 20:15:51 +00:00
7683d791f4 chore: bumps dependencies 2022-11-09 20:10:22 +00:00
649047f087 formatting 2022-10-30 13:17:56 +02:00
3aab10774e wip 2022-10-30 13:16:07 +02:00
e37f3d3d45 ignore the keys returned by the datasets Generator for storing them into an array 2022-10-30 13:05:09 +02:00
e105afce83 Updates sponsors 2022-10-26 14:36:46 +01:00
3d16183a93 Merge pull request #592 from dansysanalyst/fix_windows_backtrace
[2.x] Fix Backtrace cross-plataform compatibility
2022-09-28 11:22:28 +01:00
129325db8e Add windows support to Backtrace 2022-09-27 22:47:02 +02:00
f214a78c75 Removes symlinked autoload code 2022-09-25 23:40:21 +01:00
42c10f9a62 Rebuilds snapshots 2022-09-25 16:37:13 +01:00
8d4fa06ba3 Removes unused code 2022-09-25 16:36:32 +01:00
a73744c081 Fixes symlinked autoloads 2022-09-25 11:41:13 +01:00
e2ab53ed53 Fixes symlinked namespaces 2022-09-25 11:40:56 +01:00
5c4e98cf0c Docs 2022-09-25 11:40:38 +01:00
6bad9d302b Fixes coverage size 2022-09-25 11:38:25 +01:00
db00fc8c09 Renamed DatasetAlreadyExists exception 2022-09-22 11:01:31 +02:00
98b04632ce remove hardcoded string 2022-09-22 10:59:31 +02:00
cbd4cefc1a Update src/Repositories/DatasetsRepository.php
Co-authored-by: Luke Downing <lukeraymonddowning@gmail.com>
2022-09-22 10:56:47 +02:00
84b8c389b2 remove hardcoded string 2022-09-22 10:56:03 +02:00
e34364d8b1 remove dump 2022-09-22 10:35:57 +02:00
80854d5f87 Add DatasetInfo support class 2022-09-22 10:33:15 +02:00
cbee6e76b0 [feat] scoped datasets 2022-09-20 00:11:37 +02:00
12618ff8b3 Update CHANGELOG.md 2022-09-19 11:07:24 +01:00
115bb551df Merge pull request #583 from fabio-ivona/add-failed-expectations-message
[2.x] Add custom message to failed expectations
2022-09-19 11:03:56 +01:00
3799dc7a2a Update Expectation.php 2022-09-19 11:03:30 +01:00
a8b8adafdf Any Matcher refactor 2022-09-19 12:01:44 +02:00
961e44b5ad revert expectation failure message arg back to $message 2022-09-19 11:10:51 +02:00
20e99af194 Merge branch '2.x' into add-failed-expectations-message 2022-09-19 11:01:05 +02:00
1c888ce055 Update TODO.md 2022-09-19 10:00:17 +01:00
f3a748fee3 create Any matcher 2022-09-19 11:00:13 +02:00
2a68234bd3 Update TODO.md 2022-09-19 09:56:57 +01:00
8275d7e08d update success snapshot 2022-09-19 10:54:39 +02:00
e9630ff9a6 remove duration from success snapshot 2022-09-19 10:50:15 +02:00
97431f2622 fix tests when coverage is not available 2022-09-19 10:50:13 +02:00
df8a64b017 fix lint composer command 2022-09-19 10:50:13 +02:00
accfe86eaa Merge pull request #582 from fabio-ivona/remove-duration-from-success-snapshot
[2.x] Remove duration from success snapshot
2022-09-19 09:42:19 +01:00
667918905f Merge pull request #580 from fabio-ivona/fix-tests-when-xdebug-not-installed
[2.x] Fix tests when coverage is not available
2022-09-19 09:42:05 +01:00
c1231c9bde Merge pull request #581 from fabio-ivona/fix-lint-composer-command
[2.x] Fix lint composer command
2022-09-19 09:41:51 +01:00
bac941715a rename message key to failureMessage 2022-09-19 10:37:37 +02:00
8a3caa5e7f add custom message to failed expectations 2022-09-19 09:03:27 +02:00
7bc513bc2b remove duration from success snapshot 2022-09-19 08:55:45 +02:00
eecc598471 fix lint composer command 2022-09-19 08:48:30 +02:00
d8d1baf945 fix tests when coverage is not available 2022-09-19 08:45:48 +02:00
c5cb1fc325 Rebuilds snapshots 2022-09-18 21:55:18 +01:00
93a118a532 Adds missing dev dependencies 2022-09-18 21:46:06 +01:00
d6ddd7326a Removes laravel stubs 2022-09-18 16:33:19 +01:00
fa1145dced Moves Laravel stuff to plugin 2022-09-18 15:59:29 +01:00
06ef57060f Uses Collision v7.x 2022-09-18 15:20:36 +01:00
921d580d47 Requires pest/pest-plugin 2.x 2022-09-18 14:24:16 +01:00
7c7c8358e9 Uses dev tools 2022-09-18 14:01:47 +01:00
9b54b61e2e Updates todo 2022-09-18 13:43:51 +01:00
b93e3524f5 Bumps dusk 2022-09-18 12:20:33 +01:00
b6598af59f Improves init command 2022-09-18 11:54:07 +01:00
4de7284657 Improves help command 2022-09-18 11:10:18 +01:00
0e0e2adfbe Fixes --version and --help 2022-09-17 23:47:47 +01:00
08b62f6633 Skips coverage 2022-09-16 19:49:07 +01:00
b0b83505af Adds --profile 2022-09-16 19:11:59 +01:00
3d5271f512 Improves coverage feedback 2022-09-16 17:47:32 +01:00
462982bb28 ci: fix Static Tests workflow 2022-09-16 16:51:41 +01:00
245f636fa8 Removes duration 2022-09-16 16:43:11 +01:00
9fd8610390 Adds compact printer 2022-09-16 16:27:23 +01:00
7a41a540f2 Fixes covers tests 2022-09-16 12:34:20 +01:00
fb588711ef Fixes tests 2022-09-16 11:40:19 +01:00
1e7e164d84 Removes non-readonly property 2022-09-16 11:28:25 +01:00
036f2de795 Removes non-used configuration 2022-09-16 11:27:42 +01:00
45011ebd14 Code quality improvements 2022-09-16 11:27:17 +01:00
e9564febaf Migrates to Pint 2022-09-16 10:45:53 +01:00
579b975318 Fixes --filter 2022-09-15 23:55:54 +01:00
d8f3e9c313 Avoids extra line at the end 2022-09-15 23:55:39 +01:00
01ccbfe254 Improves display of memory plugin 2022-09-15 23:55:26 +01:00
af82c1005a Adds todo 2022-09-15 23:08:32 +01:00
02e3b5aa77 Adds todo 2022-09-15 23:08:32 +01:00
fb378bed56 wip 2022-09-15 21:19:48 +01:00
1454bcf165 Updates snapshots 2022-09-15 21:17:02 +01:00
f20a7cc9e4 Removes non-used tests 2022-09-15 21:16:28 +01:00
b21bfe3666 Removes non-unused code 2022-09-15 21:12:22 +01:00
0669423138 Style 2022-09-15 21:11:42 +01:00
3f111708e3 Verboses integration tests 2022-09-15 21:10:25 +01:00
a1f47f1a90 Verboses integration 2022-09-15 21:08:35 +01:00
24a17cfb7c Adds sponsro 2022-09-15 21:03:09 +01:00
bee4eda3ef Adjusts tests to new printer 2022-09-15 20:54:46 +01:00
797ebb2986 Style 2022-09-15 14:31:53 +01:00
ddb75441e7 Fixes types 2022-09-15 14:18:10 +01:00
c61d70abf1 Removes possibility of tests being risky 2022-09-15 14:13:18 +01:00
0953ae431e Fixes ignorable tests 2022-09-15 14:10:25 +01:00
b3a2e6026f Enables integration test on CI 2022-09-15 12:08:18 +01:00
46ffdf9c7a Enables integration 2022-09-15 12:07:32 +01:00
99e607dcfb Reverts changes on snapshot 2022-09-15 11:29:54 +01:00
1006b9cb7b Style 2022-09-15 11:29:45 +01:00
6769231b00 Fixes using test cases on uses with tests 2022-09-15 09:13:45 +01:00
3ff95faaaa Uses Collision ^7.0 2022-09-15 01:07:15 +01:00
eab944023c Upgrades dependencies 2022-09-11 13:46:06 +01:00
00d3c735fe Merge pull request #566 from fabio-ivona/2.x-dynamic-properties-fix
2.x dynamic properties fix
2022-08-29 15:14:41 +01:00
173a72e69d revert unwanted code 2022-08-29 15:51:22 +02:00
38a82cd142 fix types 2022-08-29 14:04:47 +02:00
42ceddf374 fix tests 2022-08-29 13:13:33 +02:00
6252e288b9 add php8.2 tests 2022-08-29 12:45:08 +02:00
8594980dae fix Dynamic Properties attribute 2022-08-29 12:44:45 +02:00
c22fed89f3 Merge pull request #564 from fabio-ivona/2.x-dynamic-properties
[2.x] Allow Dynamic Properties
2022-08-26 11:02:46 +01:00
429660fe57 fix tests 2022-08-26 11:38:36 +02:00
a4ec4b2841 fix tests against PhpUnit 10 refactors 2022-08-25 16:20:00 +02:00
c0d66b7dc7 allow dynamic properties 2022-08-25 15:56:14 +02:00
787e7f6ce5 Adds sponsors 2022-08-13 00:06:37 +01:00
2c338468bc Add Localazy as sponsor 2022-08-11 15:42:01 +01:00
d8b456d89a Bumps dependencies 2022-08-08 20:53:28 +01:00
bff18ab381 Merge pull request #551 from fabio-ivona/fix-phpunit-printer-refactor
fix lint
2022-08-02 11:06:03 +02:00
4e95a65af1 fix lint 2022-08-02 11:03:06 +02:00
1706e1d2e0 fix lint 2022-08-02 11:01:17 +02:00
324823fef0 Merge pull request #534 from fabio-ivona/fix-phpunit-printer-refactor
Fix issues after PHPUnit refactoring
2022-08-02 10:58:54 +02:00
afedf83f5d fix PhpUnit allowiing abstract testcases no more 2022-06-23 15:32:38 +02:00
402995bf29 implement our own Printer class because PhpUnit DefaultPrinter has become final 2022-06-23 14:24:10 +02:00
c0fc52f719 chore: removes team printer while printer stuff is not ready 2022-06-10 09:31:39 +01:00
41dbc947fa Merge pull request #520 from abenerd/feature/new-expectation
add new expectation
2022-05-24 08:00:25 +01:00
fe09184c9c Bump dependencies 2022-05-20 13:47:56 +01:00
4821bd4423 fix: high order tests debug 2022-05-12 20:05:36 +01:00
1461c9ba88 chore: updates composer dependencies 2022-05-12 19:44:45 +01:00
452d4b26b9 add new expectation 2022-05-08 11:13:45 +02:00
3dd7b677bd Merge pull request #517 from fgaroby/make_toHaveKeys_accepts_multi-dimensional_arrays
Make 'toHaveKeys' accept multi-dimensional associative arrays
2022-05-06 15:29:30 +02:00
1e86dcecd0 Better PHPStan types 2022-04-30 11:08:42 +02:00
949ba1f298 fix PHPStan types 2022-04-29 23:06:29 +02:00
77b7181b08 Make 'toHaveKeys' accept multi-dimensional associative arrays 2022-04-29 15:17:36 +02:00
6e120d60f6 Merge pull request #508 from danilopolani/feat/drop-php8.0-support
Drop support for PHP 8.0
2022-04-01 17:10:07 +01:00
10ff36480a drop support for php 8.0 2022-04-01 18:05:04 +02:00
ffb20c9956 Merge pull request #506 from danilopolani/feat/phpunit-tests-folder
[2.x] Read tests folder from PHPUnit.xml file
2022-04-01 16:39:09 +01:00
751a532124 start ConfigLoader tests 2022-04-01 14:34:43 +02:00
80c411be44 add config loader to read phpunit file and get the tests directory or fallback 2022-03-30 15:39:50 +02:00
db5c11d96e Merge pull request #449 from pestphp/performs_no_expectations
[2.x] Adds support for chaining `hasNoExpectations` to the test method.
2022-03-22 13:23:08 +00:00
f3ed9bdf8e Merge branch 'master' into performs_no_expectations
# Conflicts:
#	src/PendingCalls/TestCall.php
2022-03-22 13:20:31 +00:00
53c20d9cd2 Renames method to throwsNoExceptions. 2022-03-22 13:19:23 +00:00
fca0c8fc0c Adds sponsor 2022-03-11 19:58:21 +00:00
c8aa204ee0 Merge pull request #498 from danilopolani/fix/types
Fix phpstan static errors
2022-03-09 10:51:55 +00:00
a3889110f1 fix phpstan types 2022-03-09 11:48:35 +01:00
cda4665979 Merge pull request #492 from danilopolani/feat/covers-attribute
[2.x] Add `covers` attribute
2022-03-09 10:24:58 +00:00
09e2a26b7d fix linting 2022-03-09 11:09:03 +01:00
3dc451cf44 fix typo 2022-03-09 11:08:11 +01:00
3795870150 move coversNothing to method annotations 2022-03-09 11:05:10 +01:00
24204adc09 Adds StandWithUkraine banner 2022-03-09 01:11:37 +00:00
a027e24e3c fix typo 2022-03-08 15:02:30 +01:00
15e2e1711b enforce class-string for attributes 2022-03-08 14:57:17 +01:00
d363321db5 Merge pull request #497 from pestphp/feature/style
style: apply fixes from PHP-CS-Fixer
2022-03-08 10:58:16 +00:00
3ffed844a6 style: apply fixes from PHP-CS-Fixer 2022-03-08 10:51:38 +00:00
9cf1005183 Merge pull request #496 from hebinet/dump-for-expections
[1.x] Add `dump` helper to Expectation class
2022-03-08 10:24:56 +00:00
3601d01bd5 Added dump helper to Expectation class 2022-03-08 10:41:09 +01:00
d0136b63d4 fix linting 2022-03-07 18:23:06 +01:00
00029c15ef add generic covers method to accept both classes and functions 2022-03-07 18:22:30 +01:00
a5cbdea868 fix phpstan issues 2022-03-07 17:51:47 +01:00
edd1d890ca replace double foreach with a filter and reduce 2022-03-07 17:51:39 +01:00
985bbf4ea5 add tests for covers attribute 2022-03-07 17:40:43 +01:00
443f848386 fix fqn for coversClass 2022-03-07 17:40:29 +01:00
32dbac87c8 fix fqn on coversClass attribute and array evaluation 2022-03-07 16:18:55 +01:00
1dc33070fe fix phpdoc 2022-03-06 19:02:02 +01:00
1079793ccf cleanup 2022-03-05 17:25:23 +01:00
21364779f9 move covers attribute above the class 2022-03-05 17:23:03 +01:00
50d8688b79 allow multiple values on coversClass and coversFunction 2022-03-05 17:06:00 +01:00
27baad82d0 remove method name on coversClass 2022-03-05 17:03:45 +01:00
a894386b49 pass methods name to attribute surrounded by quotes 2022-03-05 16:51:12 +01:00
2465b88462 add covers list and attributes mutator 2022-03-05 16:23:05 +01:00
7660517f7c start covers attribute implementation 2022-03-04 22:28:37 +01:00
74470ec96d Merge pull request #491 from def-studio/fix-cs
fix code style
2022-03-04 17:37:26 +01:00
03ccea8978 fix code style 2022-03-04 17:36:02 +01:00
ed89689425 Merge pull request #490 from mkohei/feat/add-code-to-the-throws-arg
feat: Add code to the throws arg
2022-03-04 15:27:33 +00:00
62d7652376 Merge pull request #489 from owenvoke/bugfix/phpstan-fixes
fix: resolve PHPStan warnings
2022-03-04 08:56:52 +00:00
0e85921964 feat: Add code to the throws arg 2022-03-04 13:05:55 +09:00
51ec80f11f fix: resolve PHPStan warnings 2022-03-03 18:20:24 +00:00
e08d6f2803 Merge pull request #488 from owenvoke/feature/ci-ansi
ci: update to use ANSI output
2022-03-03 16:08:47 +00:00
22030bffd0 ci: update to use ANSI output 2022-03-03 16:01:09 +00:00
bc105bc818 Merge pull request #484 from fabio-ivona/fix-restore-missing-exception-in-v2
Fix toThrow expectation passing when exception doesn't exist
2022-03-03 14:34:09 +00:00
2c3a296040 fix types 2022-02-18 16:42:34 +01:00
10b204e19d handles toThrow exception with a "class not found" error 2022-02-18 16:40:31 +01:00
36130eb7a0 adds a failing test 2022-02-18 16:40:17 +01:00
6d5a8a9235 Merge branch 'master' into performs_no_expectations 2022-02-10 17:26:15 +00:00
04663e0c8e Merge pull request #474 from def-studio/phpstan-fix
phpstan fix
2022-02-05 11:36:21 +00:00
6f9ebe04b0 phpstan fix 2022-02-05 11:36:02 +01:00
8ca4caaffa Merge pull request #467 from pestphp/scoped
Adds support for `scoped` in HigherOrderExpectations
2022-01-23 23:48:10 +00:00
8baaf80691 Merge branch 'master' into scoped
# Conflicts:
#	src/Expectations/HigherOrderExpectation.php
#	tests/Features/Expect/HigherOrder/methods.php
2022-01-23 23:44:04 +00:00
e91c85496f Adds support for scoped in HigherOrderExpectations. 2022-01-23 23:37:03 +00:00
6bf92d20ad Merge pull request #465 from pestphp/v2_json_fix
Makes `json` expectation usable in Higher Order Tests
2022-01-23 23:29:35 +00:00
35f607c46c Merge pull request #466 from pestphp/rename_tap
Renames `tap` to `defer`.
2022-01-23 23:29:06 +00:00
b99f65d936 Renames tap to defer. 2022-01-23 23:18:55 +00:00
ead2dfd0a9 Makes json expectation usable in Higher Order Tests 2022-01-23 23:10:14 +00:00
d2ca6e630d Merge pull request #460 from danharrin/feature/each-keys
feature: Pass `each()` keys to closures
2022-01-20 10:36:09 +00:00
12b48a6cf6 move to new test 2022-01-20 10:34:32 +00:00
635a71ce66 feature 2022-01-20 10:21:57 +00:00
b99c4d611b test 2022-01-20 10:21:54 +00:00
af8886c062 Merge pull request #457 from cerbero90/feature/avoid-nested-expectations
Avoid nested expectations with `and()`
2022-01-16 13:53:22 +00:00
30b1f6cd0a Add test for and() 2022-01-16 22:32:55 +10:00
d24091d224 Fix return docblock 2022-01-16 22:32:38 +10:00
b3d3b4485d Do not nest expectations 2022-01-16 22:32:20 +10:00
108fe45164 chore: uses collision v5.11.0 2022-01-10 17:20:12 +00:00
e1a30e3c92 Add method comment. 2021-12-08 09:01:16 +00:00
d969eaac2c Adds support for chaining hasNoExpectations to the test method. 2021-12-08 08:58:42 +00:00
dd081c59b7 refacto: memory plugin 2021-12-05 17:48:51 +00:00
b0264886c9 Merge pull request #448 from pestphp/feat/retry
feat: adds `--retry` option
2021-12-05 17:44:26 +00:00
6dcdfdb82f Merge branch 'master' into feat/retry 2021-12-05 17:44:20 +00:00
e7d75365fd Merge pull request #433 from owenvoke/feature/memory-usage
feat: add `--memory` usage flag
2021-12-05 17:37:53 +00:00
266447bcc0 feat: add --memory usage flag 2021-12-05 17:22:21 +00:00
b74a688677 tests: style 2021-12-05 14:59:07 +00:00
b1f9ce2283 refacto: structure 2021-12-05 14:40:08 +00:00
e64b6fe924 refacto: pipes 2021-12-05 14:21:11 +00:00
c8697e0310 chore: fixes style 2021-12-05 14:05:01 +00:00
98db677646 refacto: pipes 2021-12-05 14:03:09 +00:00
6c3a8be049 Merge pull request #430 from def-studio/next-1
[v2.x] expectation pipes and interceptors
2021-12-05 12:27:20 +00:00
129a733888 chore: re-adds removed tests 2021-12-04 21:23:48 +00:00
5aeda553a4 fix: removes json file 2021-12-04 21:21:47 +00:00
0146186ddb fix: adds retry.json to gitignore 2021-12-04 21:20:28 +00:00
106b279ed0 feat: adds --retry option 2021-12-04 21:18:55 +00:00
05f44ed84a fix phpstan 2021-11-29 10:06:00 +01:00
6e7890c206 fix phpstan 2021-11-29 09:58:48 +01:00
2d2760e15c fix phpstan 2021-11-29 09:52:18 +01:00
b2eb69cbc1 fix phpstan 2021-11-29 09:48:10 +01:00
0fc90ec181 Merge branch 'master' into next-1 2021-11-29 09:36:07 +01:00
337d55b9ab lint 2021-11-29 09:25:00 +01:00
9f1e3dadf4 Merge pull request #424 from pestphp/better-dataset-support
[2.x] Vastly improves the logic around bound datasets to make them more user friendly
2021-11-27 20:07:25 +00:00
24943e5fbb Merge branch 'master' into better-dataset-support 2021-11-27 19:55:14 +00:00
33d1579660 Updates from main 2021-11-27 19:54:39 +00:00
8047ae570d chore: fixes cs 2021-11-27 19:54:21 +00:00
e236bf3821 chore: runs static workflows on php81 2021-11-27 19:51:58 +00:00
32c2df0444 chore: runs static workflows on php81 2021-11-27 19:44:41 +00:00
7a0e841a0d chore: fixes tests 2021-11-27 19:41:50 +00:00
ce2dcc3128 Merge branch 'master' into better-dataset-support
# Conflicts:
#	src/Concerns/Testable.php
2021-11-27 19:28:39 +00:00
52314a4928 Merge pull request #444 from pestphp/better_expectation_support
[2.x] Improved generics for higher order
2021-11-27 19:27:06 +00:00
d65cc9be84 Improved generics for higher order 2021-11-27 19:20:29 +00:00
beb14ce5f4 Improved generics for higher order 2021-11-27 18:48:58 +00:00
c49700dd47 Merge pull request #443 from pestphp/tweaks
Small Tweaks for Pest v2
2021-11-27 11:30:52 +00:00
3d0f267a5c Moves method evaluation to the method factory 2021-11-27 08:08:09 +00:00
729d7c4bef Small tweaks for PHP 8 2021-11-27 07:44:35 +00:00
d10b8f5d2c Merge branch 'master' into next-1 2021-11-26 16:25:40 +01:00
8494d4566a fix tests 2021-11-26 16:15:36 +01:00
7fd9cfa2e9 refactor 2021-11-26 15:44:35 +01:00
3df78f2edf Merge branch 'pipes-and-interceptors' into next-1
# Conflicts:
#	src/Concerns/Extendable.php
#	src/CoreExpectation.php
#	src/Expectation.php
#	src/Support/ExpectationPipeline.php
#	src/Support/Extendable.php
2021-11-26 15:31:40 +01:00
5f0752e874 applied changes from code review 2021-11-26 15:29:21 +01:00
cdd67a6900 merge from master 2021-11-26 15:29:21 +01:00
fce24ef01f Port from 1.x 2021-11-26 15:29:18 +01:00
47264416b1 Update FUNDING.yml 2021-11-26 15:29:18 +01:00
86dca12c09 refacto(phpstan-to-8): few adjustments 2021-11-26 15:29:18 +01:00
0b5cea6df1 upgrade to phpstan lvl 9 2021-11-26 15:29:18 +01:00
9258dcc988 fix phpstan failure 2021-11-26 15:29:18 +01:00
24edab45b1 fix tests 2021-11-26 15:29:18 +01:00
f3371e51fe wip toward lvl9 2021-11-26 15:29:18 +01:00
83b9f86972 upgrade to phpstan lvl 8 2021-11-26 15:29:15 +01:00
ca30677c53 upgrade to phpstan lvl 7 2021-11-26 15:29:15 +01:00
b205b8e748 trying to disable phpstan parallel processing 2021-11-26 15:29:15 +01:00
b9b9de1945 upgrade to phpstan level 6 2021-11-26 15:29:15 +01:00
22895ce682 docs: updates release 2021-11-26 15:29:15 +01:00
3829623984 Port from 1.x 2021-11-25 16:49:10 +00:00
3f3bc525bc Update FUNDING.yml 2021-11-21 18:25:07 +00:00
ece0930319 Merge pull request #438 from def-studio/phpstan-to-8
Phpstan to 9
2021-11-18 23:41:02 +00:00
94585789dc refacto(phpstan-to-8): few adjustments 2021-11-18 23:39:37 +00:00
7ea6d8a35d upgrade to phpstan lvl 9 2021-11-18 23:27:37 +01:00
9dd40e4610 fix phpstan failure 2021-11-18 01:14:57 +01:00
8cdca8d012 fix tests 2021-11-18 01:04:59 +01:00
7bcd3ebaee wip toward lvl9 2021-11-18 01:01:56 +01:00
d4a8a3ec37 upgrade to phpstan lvl 8 2021-11-18 00:12:39 +01:00
f460cceeba docs: updates release 2021-11-17 10:55:45 +00:00
f3f58c7f52 upgrade to phpstan lvl 7 2021-11-15 22:20:00 +01:00
f2a9b73b83 trying to disable phpstan parallel processing 2021-11-15 20:54:47 +01:00
ffd4e6d577 upgrade to phpstan level 6 2021-11-15 12:23:53 +01:00
5287eff507 merge from master 2021-11-15 11:54:42 +01:00
f6004e07c1 chore: ignores windows builds 2021-11-14 21:45:13 +00:00
b2cd60395f chore: phpstan level 5 2021-11-14 21:39:24 +00:00
183f975166 chore: phpstan level 5 2021-11-14 21:23:02 +00:00
8ace01b6f1 Merge branch 'next' 2021-11-14 20:00:34 +00:00
4b213d63bd feat: reworks evalution of Test Case 2021-11-14 19:58:25 +00:00
da5c21de8f Update README.md 2021-11-09 01:42:10 +00:00
408ae4cad8 docs: adds spatie.be as platinum sponsor 2021-11-08 18:31:48 +00:00
fc2484a28a Update README.md 2021-11-05 12:00:56 +01:00
be58d5517a improves static analysis 2021-11-01 10:40:39 +01:00
602403eb59 Update src/Concerns/Extendable.php
Co-authored-by: Luke Downing <lukeraymonddowning@gmail.com>
2021-11-01 10:31:30 +01:00
5f1776829b Update src/Concerns/Extendable.php 2021-10-31 22:16:02 +01:00
4a22c5d673 addresses reviews 2021-10-31 22:08:34 +01:00
3943919709 implemements pipelines and adds tests for it 2021-10-31 15:23:28 +01:00
8174f2d973 extracted Expectations to a CoreExpectation class 2021-10-30 20:14:54 +02:00
22d16d54c6 Merge branch 'next' into better-dataset-support
# Conflicts:
#	src/Concerns/Testable.php
2021-10-27 18:28:51 +01:00
cd34f0ba81 refactor: comments 2021-10-24 22:39:35 +01:00
648c6c5a27 refactor: comments 2021-10-24 19:37:29 +01:00
2b687a7269 refactor: PHP 8 features 2021-10-24 18:29:59 +01:00
e8c2fe6e35 fix: test class finder 2021-10-24 01:16:05 +01:00
88dfabc633 chore: points towards 2.0 2021-10-24 01:11:04 +01:00
a35bf249a0 chore: points towards 2.0 2021-10-24 01:06:48 +01:00
cf47b45262 feat: basic PHPUnit 10 support 2021-10-24 01:03:18 +01:00
eed3ed5513 Vastly improves the logic around bound datasets to make them more user friendly. 2021-10-19 21:40:40 +01:00
de46ee0f64 fix: adds link to xdebug coverage mode 2021-10-10 15:33:27 +01:00
1e011c7b40 fix: warns about xdebug modes 2021-10-10 15:25:02 +01:00
04dcebf3aa Revert "Merge pull request #413 from def-studio/fix_skipping_tests_with_exception_asserting"
This reverts commit e853792a59, reversing
changes made to 205238fcbf.
2021-10-10 14:40:07 +01:00
0346450a51 adds test to check if pipes can add parameters to an expectation 2021-10-10 01:09:45 +02:00
fc53f08e37 implemented pipe closure with $next as the first parameter 2021-10-10 01:02:04 +02:00
bc4e5b9b4e implemented pipe closure with $next as the last parameter 2021-10-10 00:16:21 +02:00
c3a445534b adds tests 2021-10-09 12:03:12 +02:00
70877bfad4 updated snapshots 2021-10-09 10:26:22 +02:00
55376d32e5 moved decorate implementation to dedicated intercept and pipe calls 2021-10-09 10:22:24 +02:00
8835502074 lint 2021-10-08 16:38:16 +02:00
ba9b06adf3 fix tests 2021-10-08 16:35:31 +02:00
e92d9bfaae implements decorators pipeline 2021-10-08 15:29:35 +02:00
d802e88148 moved old Expectation in CoreExpectation.php and made Expectation.php a decorator for it 2021-10-07 22:59:46 +02:00
e853792a59 Merge pull request #413 from def-studio/fix_skipping_tests_with_exception_asserting
Fix skipping tests with exception asserting
2021-10-02 12:01:04 +01:00
b0fbe54181 removes duplicated test 2021-10-02 09:15:52 +02:00
2a649bdfc0 revert previous solution and invert chain and proxy calls in TestCaseFactory.php 2021-10-02 09:09:47 +02:00
e042bf7d3a removes expection expectations if test is marked as skipped 2021-10-01 15:52:45 +02:00
3ff71a4563 adds failing test 2021-10-01 15:52:02 +02:00
205238fcbf docs: updates release process 2021-09-25 13:56:06 +01:00
ba06c5a76d release: v1.20.0 2021-09-25 13:52:12 +01:00
78ffc491e9 chore: exclude builds on PHP 8.1 on mac os or windows 2021-09-25 13:37:06 +01:00
7f38de11b7 refactor: --ci option 2021-09-25 13:29:11 +01:00
a6e34d204c Merge pull request #405 from def-studio/add-new-ci-option-to-pest-bin
Add new --ci option to pest bin
2021-09-25 13:11:05 +01:00
66d47e4922 fix: usage on PHP 7.3 2021-09-25 09:39:17 +01:00
7d70b6e95a merge from master 2021-09-25 09:04:26 +02:00
076dcab4c5 Merge remote-tracking branch 'origin/master' into add-new-ci-option-to-pest-bin
# Conflicts:
#	tests/.snapshots/success.txt
2021-09-25 09:04:07 +02:00
6f42e336c9 merge conflict 2021-09-25 08:57:55 +02:00
0d72b5197c merge conflict 2021-09-25 08:55:57 +02:00
7691e3c602 Merge pull request #399 from mertasan/sequence
Fix: `sequence()` can generate false positives
2021-09-24 22:15:49 +01:00
b43a59868d feat: adds unless expectation 2021-09-24 22:12:08 +01:00
457972716f refactor: expectation match method 2021-09-24 22:02:18 +01:00
ae029660e3 tests: fixes snapshots 2021-09-24 21:18:30 +01:00
dc12419078 Merge pull request #407 from mertasan/method-match
Adds new `match()` method
2021-09-24 21:16:48 +01:00
f0ddd10a54 Merge branch 'master' into method-match 2021-09-24 21:16:43 +01:00
4daf7ee4ab refactor: throwsIf method 2021-09-24 21:15:31 +01:00
d60f320382 Merge pull request #371 from mertasan/throwsif
Add `throwsIf` exception
2021-09-24 21:10:56 +01:00
3c3e6b160b refactor: expectation when 2021-09-24 21:10:02 +01:00
c99f8f196e lint 2021-09-24 14:42:26 +03:00
9cc4ecd5ab Merge branch 'master' into method-match 2021-09-24 14:39:21 +03:00
8d96f975e0 update snapshots 2021-09-24 14:37:01 +03:00
7f214f9e12 use and() instead of new self 2021-09-24 13:59:00 +03:00
da258fa89f remove the warning 2021-09-24 13:55:16 +03:00
f23f857903 Merge pull request #406 from mertasan/method-when
Adds new `when()` method
2021-09-24 11:23:21 +01:00
fec11928cf update snapshots 2021-09-23 06:57:07 +03:00
f6131d042b add tests
The filename is not accepted as `match.php`
2021-09-23 06:56:47 +03:00
543b9542ae add new match() method 2021-09-23 06:55:28 +03:00
1681c1f4f8 update snapshots 2021-09-23 03:35:56 +03:00
f41c3ce9ba add tests 2021-09-23 03:35:46 +03:00
847b06e558 add when() method 2021-09-23 03:35:41 +03:00
601c4b01fc refactors to use a Plugin to parse --ci option 2021-09-22 14:53:16 +02:00
05c1c82ae2 lint 2021-09-22 11:05:04 +02:00
1bde49b3c4 types fix 2021-09-22 11:00:41 +02:00
b22f5e0c85 adds test for CI env runs 2021-09-22 10:38:21 +02:00
dd643faa5c adds a new --ci option to pest binary 2021-09-22 10:17:44 +02:00
facbf05016 docs: adds missing entry to changelog 2021-09-20 19:38:02 +01:00
58cff003d8 release: v1.19.0 2021-09-20 19:30:58 +01:00
ae997e6eee No need for --ignore-platform-req=php 2021-09-20 19:28:43 +01:00
e6c7d68def Adds php-81 support 2021-09-20 19:26:47 +01:00
0b19672963 Merge branch 'master' into throwsif 2021-09-19 01:47:07 +03:00
a16a19e121 update snapshots 2021-09-16 18:10:15 +03:00
3dd10b3c7c change test 2021-09-16 18:09:59 +03:00
12e63c7376 add new assertion 2021-09-16 18:09:45 +03:00
2f0cd7a4e3 Adds Fathom Analytics as sponsor 2021-09-15 10:03:05 +01:00
447af55e7c Merge pull request #391 from gabbanaesteban/master
Add `toHaveProperties`
2021-09-03 09:52:39 +01:00
253e9d10c8 Fix types 2021-09-03 04:44:10 -04:00
536ce1eca0 Update snapshots 2021-09-03 04:40:48 -04:00
4331b2aaf6 Add toHaveProperties 2021-09-03 04:26:57 -04:00
8d99cacc95 Update README.md 2021-09-02 23:36:02 +01:00
3c38facc8a Update README.md 2021-09-02 23:35:51 +01:00
ed389d35d0 Adds Auth0 as sponsor 2021-09-02 23:35:40 +01:00
60c0636523 release: v1.18.0 2021-08-30 00:05:26 +01:00
16b6f96b47 Merge pull request #389 from dansysanalyst/toHaveLength-mblen
Use mb_strlen instead of grapheme_strlen
2021-08-29 12:18:38 +01:00
042f2ec3f3 Use mb_strlen instead of grapheme_strlen
Due to inconsistent behave, mb_strlen will be used.
2021-08-29 12:55:37 +02:00
851ce36010 Merge pull request #386 from dansysanalyst/expectation-toHaveLength
Adds toHaveLength() Expectation & Tests
2021-08-29 00:45:39 +01:00
4f386894bd Revert "Use toHaveCount"
This reverts commit 2289adade2.
2021-08-28 18:14:55 +02:00
2289adade2 Use toHaveCount
changes to toHaveCount
2021-08-28 17:54:29 +02:00
29e21e3814 Merge pull request #385 from dansysanalyst/snapshot-contrib
Update snapshots
2021-08-28 16:47:25 +01:00
8367af22e7 Adds toHaveLength() Expectation & Tests
Adds toHaveLength() Expectation and its tests. toHaveLength checks if the given value has the informed length. Works with strings, array, object and collections.
2021-08-28 17:02:09 +02:00
e3d678dc04 Update snapshots
Adds update:snapshots to CONTRIBUTING
2021-08-28 16:30:17 +02:00
4ae482c707 feat: adds support for nunomaduro/collision:^6.0 2021-08-27 22:32:18 +01:00
075c31bc78 release: v1.17.0 2021-08-26 21:17:03 +01:00
2125bf9668 chore: adjusts tests 2021-08-26 21:14:56 +01:00
dbf3c0a8cf Merge pull request #361 from kbond/throw-expectation
Add `toThrow` expectation
2021-08-26 21:02:38 +01:00
1d4c1a5359 update snapshots 2021-08-07 08:22:31 +03:00
8e32b88fc8 add tests 2021-08-07 08:22:26 +03:00
1a7baad338 add throwsIf exception 2021-08-07 08:22:16 +03:00
c776bcf86d add toThrow expectation 2021-08-01 12:33:09 -04:00
343 changed files with 12612 additions and 5007 deletions

16
.gitattributes vendored
View File

@ -1,14 +1,18 @@
/art export-ignore /docker export-ignore
/docs export-ignore /docs export-ignore
/tests export-ignore /tests export-ignore
/scripts export-ignore /scripts export-ignore
/.github export-ignore /.github export-ignore
/.php-cs-fixer.dist.php export-ignore
.editorconfig export-ignore .editorconfig export-ignore
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore
phpstan.neon export-ignore /phpstan.neon export-ignore
/phpunit.xml export-ignore /phpunit.xml export-ignore
CHANGELOG.md export-ignore /CHANGELOG.md export-ignore
CONTRIBUTING.md export-ignore /CONTRIBUTING.md export-ignore
README.md export-ignore /docker-compose.yml export-ignore
/Makefile export-ignore
/rector.php export-ignore
/README.md export-ignore
/RELEASE.md export-ignore

4
.github/FUNDING.yml vendored
View File

@ -1,4 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [nunomaduro,owenvoke,olivernybroe,octoper,lukeraymonddowning] github: [nunomaduro]
patreon: nunomaduro custom: https://www.paypal.com/paypalme/enunomaduro

View File

@ -1,63 +0,0 @@
name: Changelog
on:
push:
branches: [ master ]
paths:
- CHANGELOG.md
- .github/workflows/changelog.yml
pull_request:
branches: [ master ]
paths:
- CHANGELOG.md
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'pestphp/pest'
steps:
- uses: actions/checkout@v2
- name: Checkout website repository
uses: actions/checkout@v2
with:
token: ${{ secrets.CHANGELOG_KEY }}
repository: pestphp/docs
path: pestphp-docs
ref: master
- name: Read CHANGELOG.md
id: package
uses: juliangruber/read-file-action@v1
with:
path: ./CHANGELOG.md
- name: Add file headers
uses: DamianReeves/write-file-action@v1.0
with:
path: ./CHANGELOG.md
contents: |
---
title: Changelog
description: Changelog
---
${{ steps.package.outputs.content }}
----
Next section: [Upgrade Guide →](/docs/upgrade-guide)
write-mode: overwrite
- name: Copy CHANGELOG to website repository
run: cp CHANGELOG.md pestphp-docs/changelog.md
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.CHANGELOG_KEY }}
commit-message: Update changelog.md
committer: GitHub Action <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
title: 'Update changelog.md'
path: ./pestphp-docs

View File

@ -1,38 +1,20 @@
name: Static Analysis name: Static Analysis
on: ['push', 'pull_request'] on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'
jobs: jobs:
cs: static:
runs-on: ubuntu-latest name: Static Tests
name: Code Style
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
tools: composer:v2
coverage: none
- name: Install Dependencies
run: composer update --no-interaction --no-progress
- name: Run PHP-CS-Fixer
run: vendor/bin/php-cs-fixer fix -v --allow-risky=yes --dry-run
phpstan:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
dependency-version: [prefer-lowest, prefer-stable] dependency-version: [prefer-lowest, prefer-stable]
name: PHPStan ${{ matrix.dependency-version }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -40,12 +22,18 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.1
tools: composer:v2 tools: composer:v2
coverage: none coverage: none
- name: Install Dependencies - name: Install Dependencies
run: composer update --prefer-stable --no-interaction --no-progress run: composer update --prefer-stable --no-interaction --no-progress --ansi
- name: Run PHPStan - name: Types
run: vendor/bin/phpstan analyse --no-progress run: composer test:types
- name: Refacto
run: composer test:refacto
- name: Style
run: composer test:lint

View File

@ -1,6 +1,11 @@
name: Tests name: Tests
on: ['push', 'pull_request'] on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'
jobs: jobs:
ci: ci:
@ -8,11 +13,10 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
php: ['7.3', '7.4', '8.0'] php: ['8.1', '8.2']
dependency-version: [prefer-lowest, prefer-stable] dependency-version: [prefer-lowest, prefer-stable]
parallel: ['', '--parallel']
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} - ${{ matrix.parallel }} name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
steps: steps:
- name: Checkout - name: Checkout
@ -30,16 +34,15 @@ jobs:
echo "::add-matcher::${{ runner.tool_cache }}/php.json" echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.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 run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
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 - name: Unit Tests
run: php bin/pest --colors=always --exclude-group=integration ${{ matrix.parallel }} run: composer test:unit
- name: Unit Tests in Parallel
run: composer test:parallel
if: startsWith(matrix.os, 'windows') != true
- name: Integration Tests - name: Integration Tests
run: php bin/pest --colors=always --group=integration run: composer test:integration

2
.gitignore vendored
View File

@ -1,9 +1,11 @@
.idea/* .idea/*
.idea/codeStyleSettings.xml .idea/codeStyleSettings.xml
.temp/*
composer.lock composer.lock
/vendor/ /vendor/
coverage.xml coverage.xml
.phpunit.result.cache .phpunit.result.cache
.phpunit.cache
/.php-cs-fixer.php /.php-cs-fixer.php
.php-cs-fixer.cache .php-cs-fixer.cache
.temp/coverage.php .temp/coverage.php

View File

@ -1,30 +0,0 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__ . DIRECTORY_SEPARATOR . 'tests')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'bin')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs')
->in(__DIR__ . DIRECTORY_SEPARATOR . 'src')
->append(['.php-cs-fixer.dist.php']);
$rules = [
'@Symfony' => true,
'phpdoc_no_empty_return' => false,
'array_syntax' => ['syntax' => 'short'],
'yoda_style' => false,
'binary_operator_spaces' => [
'operators' => [
'=>' => 'align',
'=' => 'align',
],
],
'concat_space' => ['spacing' => 'one'],
'not_operator_with_space' => false,
];
$rules['increment_style'] = ['style' => 'post'];
return (new PhpCsFixer\Config())
->setUsingCache(true)
->setRules($rules)
->setFinder($finder);

View File

@ -1,313 +1,35 @@
# Changelog # Release Notes for 2.x
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) ## Unreleased
and this project adheres to [Semantic Versioning](http://semver.org/).
## [v2.2.0 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.1.0...v2.2.0)
## [v1.16.0 (2021-08-19)](https://github.com/pestphp/pest/compare/v1.15.0...v1.16.0)
### Added ### Added
- Support for new parallel options ([#369](https://github.com/pestphp/pest/pull/369)) - Improved error messages on dataset arguments mismatch ([#698](https://github.com/pestphp/pest/pull/698))
- Allows the usage of `DateTimeInterface` on multiple expectations ([#716](https://github.com/pestphp/pest/pull/716))
## [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 ### Fixed
- `skip` with false condition marking test as skipped ([22b822c](https://github.com/pestphp/pest/commit/22b822ce87a3d19d84960fa5c93eb286820b525d)) - `--dirty` option on Windows environments ([#721](https://github.com/pestphp/pest/pull/721))
- Parallel exit code when `phpunit.xml` is outdated ([14dd5cb](https://github.com/pestphp/pest/commit/14dd5cb57b9432300ac4e8095f069941cb43bdb5))
## [v2.1.0 (2023-03-21)](https://github.com/pestphp/pest/compare/v2.0.2...v2.1.0)
## [v1.12.0 (2021-07-26)](https://github.com/pestphp/pest/compare/v1.11.0...v1.12.0)
### Added ### Added
- `--force` option to override tests in `pest:test` artisan command ([#353](https://github.com/pestphp/pest/pull/353)) - `only` test case method ([bcd1503](https://github.com/pestphp/pest/commit/bcd1503cade938853a55c1283b02b6b820ea0b69))
- Support for PHPUnit `^9.3.7` ([ca9d783](https://github.com/pestphp/pest/commit/ca9d783cf942a2caabc85ff7a728c7f28350c67a))
### Fixed ### Fixed
- `beforeAll` and `afterAll` behind called multiple times per test ([#357](https://github.com/pestphp/pest/pull/357)) - Issues with different characters on test names ([715](https://github.com/pestphp/pest/pull/715))
## [v1.11.0 (2021-07-21)](https://github.com/pestphp/pest/compare/v1.10.0...v1.11.0) ## [v2.0.2 (2023-03-20)](https://github.com/pestphp/pest/compare/v2.0.1...v2.0.2)
### 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 ### Fixed
- PhpStorm will no longer show 0 assertions in the output ([#349](https://github.com/pestphp/pest/pull/349)) - `Pest.php` not being loaded in certain scenarios ([b887116](https://github.com/pestphp/pest/commit/b887116e5ce9a69403ad620cad20f0a029474eb5))
## [v1.10.0 (2021-07-12)](https://github.com/pestphp/pest/compare/v1.9.1...v1.10.0) ## [v2.0.1 (2023-03-20)](https://github.com/pestphp/pest/compare/v2.0.0...v2.0.1)
### 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))
- Support for nested Higher Order Expectations ([#324](https://github.com/pestphp/pest/pull/324))
## [v1.6.0 (2021-06-18)](https://github.com/pestphp/pest/compare/v1.5.0...v1.6.0)
### Added
- Adds a new `json` expectation method to improve testing with JSON strings ([#325](https://github.com/pestphp/pest/pull/325))
- Adds dot notation support to the `toHaveKey` and `toHaveKeys` expectations ([#322](https://github.com/pestphp/pest/pull/322))
## [v1.5.0 (2021-06-15)](https://github.com/pestphp/pest/compare/v1.4.0...v1.5.0)
### Changed
- Moves plugins from the `require` section to the core itself ([#317](https://github.com/pestphp/pest/pull/317)), ([#318](https://github.com/pestphp/pest/pull/318)), ([#320](https://github.com/pestphp/pest/pull/320))
## [v1.4.0 (2021-06-10)](https://github.com/pestphp/pest/compare/v1.3.2...v1.4.0)
### Added
- Support for multiple datasets (Matrix) on the `with` method ([#303](https://github.com/pestphp/pest/pull/303))
- Support for incompleted tests ([49de462](https://github.com/pestphp/pest/commit/49de462250cf9f65f09e13eaf6dcc0e06865b930))
## [v1.3.2 (2021-06-07)](https://github.com/pestphp/pest/compare/v1.3.1...v1.3.2)
### Fixed
- Test cases with the @ symbol in the directory fail ([#308](https://github.com/pestphp/pest/pull/308))
## [v1.3.1 (2021-06-06)](https://github.com/pestphp/pest/compare/v1.3.0...v1.3.1)
### Added
- Added for PHPUnit 9.5.5 ([#310](https://github.com/pestphp/pest/pull/310))
### Changed
- Lock minimum Pest plugin versions ([#306](https://github.com/pestphp/pest/pull/306))
## [v1.3.0 (2021-05-23)](https://github.com/pestphp/pest/compare/v1.2.1...v1.3.0)
### Added
- Named datasets no longer show the arguments ([#302](https://github.com/pestphp/pest/pull/302))
### Fixed ### Fixed
- Wraps global functions within `function_exists` ([#300](https://github.com/pestphp/pest/pull/300)) - Wrong `version` configuration key on `composer.json` ([8f91f40](https://github.com/pestphp/pest/commit/8f91f40e8ea8b35e04b7989bed6a8f9439e2a2d6))
## [v1.2.1 (2021-05-14)](https://github.com/pestphp/pest/compare/v1.2.0...v1.2.1) ## [v2.0.0 (2023-03-20)](https://github.com/pestphp/pest/compare/v1.22.6...v2.0.0)
### Fixed
- Laravel commands failing with new `--test-directory` option ([#297](https://github.com/pestphp/pest/pull/297))
## [v1.2.0 (2021-05-13)](https://github.com/pestphp/pest/compare/v1.1.0...v1.2.0) Please consult the [upgrade guide](https://pestphp.com/docs/upgrade-guide) and [release notes](https://pestphp.com/docs/announcing-pest2) in the official Pest documentation.
### Added
- Adds JUnit / Infection support ([#291](https://github.com/pestphp/pest/pull/291))
- `--test-directory` command line option ([#283](https://github.com/pestphp/pest/pull/283))
## [v1.1.0 (2021-05-02)](https://github.com/pestphp/pest/compare/v1.0.5...v1.1.0)
### Added
- Possibility of "hooks" being added using the "uses" function ([#282](https://github.com/pestphp/pest/pull/282))
## [v1.0.5 (2021-03-31)](https://github.com/pestphp/pest/compare/v1.0.4...v1.0.5)
### Added
- Add `--browse` option to `pest:dusk` command ([#280](https://github.com/pestphp/pest/pull/280))
- Support for PHPUnit 9.5.4 ([#284](https://github.com/pestphp/pest/pull/284))
## [v1.0.4 (2021-03-17)](https://github.com/pestphp/pest/compare/v1.0.3...v1.0.4)
### Added
- Support for PHPUnit 9.5.3 ([#278](https://github.com/pestphp/pest/pull/278))
## [v1.0.3 (2021-03-13)](https://github.com/pestphp/pest/compare/v1.0.2...v1.0.3)
### Added
- Support for test extensions ([#269](https://github.com/pestphp/pest/pull/269))
## [v1.0.2 (2021-02-04)](https://github.com/pestphp/pest/compare/v1.0.1...v1.0.2)
### Added
- Support for PHPUnit 9.5.2 ([#267](https://github.com/pestphp/pest/pull/267))
## [v1.0.1 (2021-01-18)](https://github.com/pestphp/pest/compare/v1.0.0...v1.0.1)
### Added
- Support for PHPUnit 9.5.1 ([#261](https://github.com/pestphp/pest/pull/261))
### Fixed
- Fix `TestCase@expect` PHPDoc tag ([#251](https://github.com/pestphp/pest/pull/251))
## [v1.0.0 (2021-01-03)](https://github.com/pestphp/pest/compare/v0.3.19...v1.0.0)
### Added
- `pest:test --dusk` option ([#245](https://github.com/pestphp/pest/pull/245))
### Changed
- Stable version
- Updates init structure ([#240](https://github.com/pestphp/pest/pull/240))
## [v0.3.19 (2020-12-27)](https://github.com/pestphp/pest/compare/v0.3.18...v0.3.19)
### Fixed
- Fix binary path in `pest:dusk` command ([#239](https://github.com/pestphp/pest/pull/239))
## [v0.3.18 (2020-12-26)](https://github.com/pestphp/pest/compare/v0.3.17...v0.3.18)
### Added
- `toBeJson()` expectation ([plugin-expectations#2](https://github.com/pestphp/pest-plugin-expectations/pull/2))
## [v0.3.17 (2020-12-20)](https://github.com/pestphp/pest/compare/v0.3.16...v0.3.17)
### Fixed
- Class inheritance with `depends()` ([#236](https://github.com/pestphp/pest/pull/236))
## [v0.3.16 (2020-12-13)](https://github.com/pestphp/pest/compare/v0.3.15...v0.3.16)
### Changed
- Moves expectation API for external plugin ([5d7f262](https://github.com/pestphp/pest/commit/5d7f262f4ab280a660a85900f402eebb23abfda8))
## [v0.3.15 (2020-12-04)](https://github.com/pestphp/pest/compare/v0.3.14...v0.3.15)
### Added
- Support for PHPUnit 9.5.0 ([#234](https://github.com/pestphp/pest/pull/234))
- Support for extending expectation API ([#232](https://github.com/pestphp/pest/pull/232))
### Fixed
- Static analysis while using string as key for datasets ([#233](https://github.com/pestphp/pest/pull/233))
## [v0.3.14 (2020-11-28)](https://github.com/pestphp/pest/compare/v0.3.13...v0.3.14)
### Added
- `pest:dusk` command ([#223](https://github.com/pestphp/pest/pull/223))
- Better feedback on errors in `toMatchArray` and `toMatchObject` ([#231](https://github.com/pestphp/pest/pull/231))
## [v0.3.13 (2020-11-23)](https://github.com/pestphp/pest/compare/v0.3.12...v0.3.13)
### Added
- `toMatchArray` expectation ([7bea51f](https://github.com/pestphp/pest/commit/7bea51fe09dd2eca7093e4c34cf2dab2e8d39fa5), [3fd24d9](https://github.com/pestphp/pest/commit/3fd24d96d3145dcebdb0aab40aa8b76faa8b6979))
- Add Pest options to `--help` output ([#217](https://github.com/pestphp/pest/pull/217))
### Fixed
- Resolve issue with name resolution in `depends()` ([#216](https://github.com/pestphp/pest/pull/216))
## [v0.3.12 (2020-11-11)](https://github.com/pestphp/pest/compare/v0.3.11...v0.3.12)
### Added
- Add support for PHPUnit 9.4.3 ([#219](https://github.com/pestphp/pest/pull/219))
## [v0.3.11 (2020-11-09)](https://github.com/pestphp/pest/compare/v0.3.10...v0.3.11)
### Changed
- Improved the exception output for the TeamCity printer (usage with phpstorm plugin) ([#215](https://github.com/pestphp/pest/pull/215))
## [v0.3.10 (2020-11-01)](https://github.com/pestphp/pest/compare/v0.3.9...v0.3.10)
### Added
- Add support for PHPUnit 9.4.2 ([d177ab5](https://github.com/pestphp/pest/commit/d177ab5ec2030c5bb8e418d10834c370c94c433d))
## [v0.3.9 (2020-10-13)](https://github.com/pestphp/pest/compare/v0.3.8...v0.3.9)
### Added
- Add support for named datasets in description output ([#134](https://github.com/pestphp/pest/pull/134))
- Add Pest version to `--help` output ([#203](https://github.com/pestphp/pest/pull/203))
- Add support for PHPUnit 9.4.1 ([#207](https://github.com/pestphp/pest/pull/207))
## [v0.3.8 (2020-10-03)](https://github.com/pestphp/pest/compare/v0.3.7...v0.3.8)
### Added
- Add support for PHPUnit 9.4.0 ([#199](https://github.com/pestphp/pest/pull/199))
### Fixed
- Fix chained higher order assertions returning void ([#196](https://github.com/pestphp/pest/pull/196))
## [v0.3.7 (2020-09-30)](https://github.com/pestphp/pest/compare/v0.3.6...v0.3.7)
### Added
- Add support for PHPUnit 9.3.11 ([#193](https://github.com/pestphp/pest/pull/193))
## [v0.3.6 (2020-09-21)](https://github.com/pestphp/pest/compare/v0.3.5...v0.3.6)
### Added
- `toMatch` expectation ([#191](https://github.com/pestphp/pest/pull/191))
- `toMatchConstraint` expectation ([#190](https://github.com/pestphp/pest/pull/190))
## [v0.3.5 (2020-09-16)](https://github.com/pestphp/pest/compare/v0.3.4...v0.3.5)
### Added
- `toStartWith` and `toEndWith` expectations ([#187](https://github.com/pestphp/pest/pull/187))
## [v0.3.4 (2020-09-15)](https://github.com/pestphp/pest/compare/v0.3.3...v0.3.4)
### Added
- `toMatchObject` expectation ([4e184b2](https://github.com/pestphp/pest/commit/4e184b2f906c318a5e9cd38fe693cdab5c48d8a2))
## [v0.3.3 (2020-09-13)](https://github.com/pestphp/pest/compare/v0.3.2...v0.3.3)
### Added
- `toHaveKeys` expectation ([204f343](https://github.com/pestphp/pest/commit/204f343831adc17bb3734553c24fac92d02f27c7))
## [v0.3.2 (2020-09-12)](https://github.com/pestphp/pest/compare/v0.3.1...v0.3.2)
### Added
- Support to PHPUnit 9.3.9, and 9.3.10 ([1318bf9](https://github.com/pestphp/pest/commit/97f98569bc86e8b87f8cde963fe7b4bf5399623b))
## [v0.3.1 (2020-08-29)](https://github.com/pestphp/pest/compare/v0.3.0...v0.3.1)
### Added
- Support to PHPUnit 9.3.8 ([#174](https://github.com/pestphp/pest/pull/174))
## [v0.3.0 (2020-08-27)](https://github.com/pestphp/pest/compare/v0.2.3...v0.3.0)
### Added
- Expectation API (TODO)
- PHPUnit 9.3 and PHP 8 support ([#128](https://github.com/pestphp/pest/pull/128))
- Forwards `$this` calls to globals ([#169](https://github.com/pestphp/pest/pull/169))
### Fixed
- don't decorate output if --colors=never is set ([36b879f](https://github.com/pestphp/pest/commit/36b879f97d7b187c87a94eb60af5b7d3b7253d56))
## [v0.2.3 (2020-07-01)](https://github.com/pestphp/pest/compare/v0.2.2...v0.2.3)
### Added
- `--init` and `pest:install` artisan command output changes ([#118](https://github.com/pestphp/pest/pull/118), [db7c4b1](https://github.com/pestphp/pest/commit/db7c4b174f0974969450dda71dcd649ef0c073a3))
- `--version` option to view the current version of Pest ([9ea51ca](https://github.com/pestphp/pest/commit/9ea51caf3f74569debb1e465992e9ea916cb80fe))
## [v0.2.2 (2020-06-21)](https://github.com/pestphp/pest/compare/v0.2.1...v0.2.2)
### Added
- `depends` phpunit feature ([#103](https://github.com/pestphp/pest/pull/103))
### Fixes
- datasets name conflict ([#101](https://github.com/pestphp/pest/pull/101))
## [v0.2.1 (2020-06-17)](https://github.com/pestphp/pest/compare/v0.2.0...v0.2.1)
### Fixes
- Multiple `uses` in the same path override previous `uses` ([#97](https://github.com/pestphp/pest/pull/97))
## [v0.2.0 (2020-06-14)](https://github.com/pestphp/pest/compare/v0.1.5...v0.2.0)
### Adds
- `--init` option to install Pest on a new blank project ([70b3c7e](https://github.com/pestphp/pest/commit/70b3c7ea1ddb031f3bbfaabdc28d56270608ebbd))
- pending higher orders tests aka tests without description ([aa1917c](https://github.com/pestphp/pest/commit/aa1917c28d9b69c2bd1d51f986c4f61318ee7e16))
### Fixed
- `--verbose` and `--colors` options not being used by printers ([#51](https://github.com/pestphp/pest/pull/51))
- missing support on windows ([#61](https://github.com/pestphp/pest/pull/61))
### Changed
- `helpers.php` stub provides now namespaced functions
- functions provided by plugins are now namespaced functions:
```php
use function Pest\Faker\faker;
it('foo', function () {
$name = faker()->name;
});
```
## [v0.1.5 (2020-05-24)](https://github.com/pestphp/pest/compare/v0.1.4...v0.1.5)
### Fixed
- Missing default decorated output on coverage ([88d2391](https://github.com/pestphp/pest/commit/88d2391d2e6fe9c9416462734b9b523cb418f469))
## [v0.1.4 (2020-05-24)](https://github.com/pestphp/pest/compare/v0.1.3...v0.1.4)
### Added
- Support to Lumen on artisan commands ([#18](https://github.com/pestphp/pest/pull/18))
### Fixed
- Mockery tests without assertions being considered risky ([415f571](https://github.com/pestphp/pest/commit/415f5719101b30c11d87f74810a71686ef2786c6))
## [v0.1.3 (2020-05-21)](https://github.com/pestphp/pest/compare/v0.1.2...v0.1.3)
### Added
- `Plugin::uses()` method for making traits globally available ([6c4be01](https://github.com/pestphp/pest/commit/6c4be0190e9493702a976b996bbbf5150cc6bb53))
## [v0.1.2 (2020-05-15)](https://github.com/pestphp/pest/compare/v0.1.1...v0.1.2)
### Added
- Support to custom helpers ([#7](https://github.com/pestphp/pest/pull/7))
## [v0.1.1 (2020-05-14)](https://github.com/pestphp/pest/compare/v0.1.0...v0.1.1)
### Added
- `test` function without any arguments returns the current test case ([6fc55be](https://github.com/pestphp/pest/commit/6fc55becc8aecff685a958617015be1a4c118b01))
### Fixed
- "No coverage driver error" now returns proper error on Laravel ([28d8822](https://github.com/pestphp/pest/commit/28d8822de01f4fa92c62d8b8e019313f382b97e9))
## [v0.1.0 (2020-05-09)](https://github.com/pestphp/pest/commit/de2929077b344a099ef9c2ddc2f48abce14e248f)
### Added
- First version

View File

@ -31,6 +31,10 @@ composer lint
``` ```
## Tests ## Tests
Update the snapshots:
```bash
composer update:snapshots
```
Run all tests: Run all tests:
```bash ```bash
composer test composer test
@ -50,3 +54,22 @@ Integration tests:
```bash ```bash
composer test:integration composer test:integration
``` ```
## Simplified setup using Docker
If you have Docker installed, you can quickly get all dependencies for Pest in place using
our Docker files. Assuming you have the repository cloned, you may run the following
commands:
1. `make build` to build the Docker image
2. `make install` to install Composer dependencies
3. `make test` to run the project tests and analysis tools
If you want to check things work against a specific version of PHP, you may include
the `PHP` build argument when building the image:
```bash
make build ARGS="--build-arg PHP=8.2"
```
The default PHP version will always be the lowest version of PHP supported by Pest.

14
Makefile Normal file
View File

@ -0,0 +1,14 @@
# Well documented Makefiles
DEFAULT_GOAL := help
help:
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-40s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
build: ## Build all docker images. Specify the command e.g. via make build ARGS="--build-arg PHP=8.2"
docker compose build $(ARGS)
##@ [Application]
install: ## Install the composer dependencies
docker compose run --rm composer install
test: ## Run the tests
docker compose run --rm composer test

View File

@ -1,7 +1,7 @@
<p align="center"> <p align="center">
<img src="https://raw.githubusercontent.com/pestphp/art/master/readme.png" width="600" alt="PEST"> <img src="https://raw.githubusercontent.com/pestphp/art/master/v2/banner.png" width="600" alt="PEST">
<p align="center"> <p align="center">
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/workflow/status/pestphp/pest/Tests/master"></a> <a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=2.x&label=Tests%202.x"></a>
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a> <a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a>
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a> <a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a>
<a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a> <a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>
@ -9,22 +9,32 @@
</p> </p>
------ ------
**Pest** is an elegant PHP Testing Framework with a focus on simplicity. It was carefully crafted to bring the joy of testing to PHP. **Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
- Explore the docs: **[pestphp.com »](https://pestphp.com)** - Explore our docs at **[pestphp.com »](https://pestphp.com)**
- Follow us on Twitter: **[@pestphp »](https://twitter.com/pestphp)** - Follow us on Twitter at **[@pestphp »](https://twitter.com/pestphp)**
- Join us on the Discord Server: **[discord.gg/bMAJv82 »](https://discord.gg/bMAJv82)** - Join us at **[discord.gg/kaHY6p54JH »](https://discord.gg/kaHY6p54JH)** or **[t.me/+kYH5G4d5MV83ODk0 »](https://t.me/+kYH5G4d5MV83ODk0)**
## Pest Sponsors ## Sponsors
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). We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
### Platinum Sponsors
- **[Forge](https://forge.laravel.com)**
- **[LoadForge](https://loadforge.com)**
- **[Spatie](https://spatie.be)**
- **[Worksome](https://www.worksome.com/)**
### Premium Sponsors ### Premium Sponsors
- **[Akaunting](https://akaunting.com)** - [Akaunting](https://akaunting.com)
- **[Codecourse](https://codecourse.com/)** - [Codecourse](https://codecourse.com/)
- **[Meema](https://meema.io/)** - [Laracasts](https://laracasts.com/)
- **[Scout APM](https://scoutapm.com)** - [Localazy](https://localazy.com)
- **[Spatie](https://spatie.be/)** - [Hyvor](https://hyvor.com/)
- [Fathom Analytics](https://usefathom.com/)
- [Meema](https://meema.io)
- [Zapiet](https://www.zapiet.com)
Pest is an open-sourced software 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)**.

View File

@ -2,14 +2,17 @@
When releasing a new version of Pest there are some checks and updates that need to be done: 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` > **For Pest v1 you should use the `1.x` branch instead.**
- 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 2.x`
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...2.x](https://github.com/pestphp/pest/compare/{latest_version}...master) 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) - Update the version number in [src/Pest.php](src/Pest.php)
- Run the tests locally using: `composer test` - Run the tests locally using: `composer test`
- Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"` - Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"`
- Push the changes to GitHub - Push the changes to GitHub
- Check that the CI is passing as expected: [github.com/pestphp/pest/actions](https://github.com/pestphp/pest/actions) - 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` - 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 ### Plugins

100
bin/pest
View File

@ -1,21 +1,60 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php declare(strict_types=1); <?php declare(strict_types=1);
use NunoMaduro\Collision\Provider; use Pest\Kernel;
use Pest\Actions\ValidatesEnvironment; use Pest\Panic;
use Pest\Support\Container; use Pest\TestCaseFilters\GitDirtyTestCaseFilter;
use Pest\TestCaseMethodFilters\TodoTestCaseFilter;
use Pest\TestSuite; use Pest\TestSuite;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
(static function () { (static function () {
// Ensures Collision's Printer is registered.
$_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter';
$args = $_SERVER['argv'];
$dirty = false;
$todo = false;
foreach ($args as $key => $value) {
if ($value === '--compact') {
$_SERVER['COLLISION_PRINTER_COMPACT'] = 'true';
unset($args[$key]);
}
if ($value === '--profile') {
$_SERVER['COLLISION_PRINTER_PROFILE'] = 'true';
unset($args[$key]);
}
if (str_contains($value, '--test-directory')) {
unset($args[$key]);
}
if ($value === '--dirty') {
$dirty = true;
unset($args[$key]);
}
if ($value === '--todos') {
$todo = true;
unset($args[$key]);
}
if (str_contains($value, '--teamcity')) {
unset($args[$key]);
$args[] = '--no-output';
unset($_SERVER['COLLISION_PRINTER']);
}
}
// Used when Pest is required using composer. // Used when Pest is required using composer.
$vendorPath = dirname(__DIR__, 4) . '/vendor/autoload.php'; $vendorPath = dirname(__DIR__, 4).'/vendor/autoload.php';
// Used when Pest maintainers are running Pest tests. // Used when Pest maintainers are running Pest tests.
$localPath = dirname(__DIR__) . '/vendor/autoload.php'; $localPath = dirname(__DIR__).'/vendor/autoload.php';
if (file_exists($vendorPath)) { if (file_exists($vendorPath)) {
include_once $vendorPath; include_once $vendorPath;
@ -25,39 +64,36 @@ use Symfony\Component\Console\Output\OutputInterface;
$autoloadPath = $localPath; $autoloadPath = $localPath;
} }
(new Provider())->register();
// Get $rootPath based on $autoloadPath // Get $rootPath based on $autoloadPath
$rootPath = dirname($autoloadPath, 2); $rootPath = dirname($autoloadPath, 2);
$argv = new ArgvInput(); $input = new ArgvInput();
$testSuite = TestSuite::getInstance($rootPath, $argv->getParameterOption('--test-directory', 'tests')); $testSuite = TestSuite::getInstance(
$rootPath,
$input->getParameterOption('--test-directory', 'tests'),
);
if ($dirty) {
$testSuite->tests->addTestCaseFilter(new GitDirtyTestCaseFilter($rootPath));
}
if ($todo) {
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter());
}
$isDecorated = $input->getParameterOption('--colors', 'always') !== 'never';
$isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never';
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated); $output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated);
$container = Container::getInstance(); try {
$container->add(TestSuite::class, $testSuite); $kernel = Kernel::boot($testSuite, $input, $output);
$container->add(OutputInterface::class, $output);
ValidatesEnvironment::in($testSuite); $result = $kernel->handle($args);
$args = $_SERVER['argv']; $kernel->shutdown();
} catch (Throwable|Error $e) {
// Let's remove any arguments that PHPUnit does not understand Panic::with($e);
if ($argv->hasParameterOption('--test-directory')) {
foreach ($args as $key => $value) {
if (strpos($value, '--test-directory') !== false) {
unset($args[$key]);
}
}
} }
if (($runInParallel = $argv->hasParameterOption(['--parallel', '-p'])) && !class_exists(\Pest\Parallel\Command::class)) { exit($result);
$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));
})(); })();

100
bin/worker.php Normal file
View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
use ParaTest\WrapperRunner\ApplicationForWrapperWorker;
use ParaTest\WrapperRunner\WrapperWorker;
use Pest\Kernel;
use Pest\Plugins\Actions\CallsHandleArguments;
use Pest\TestSuite;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
$bootPest = (static function (): void {
$workerArgv = new ArgvInput();
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
$testSuite = TestSuite::getInstance($rootPath, $workerArgv->getParameterOption(
'--test-directory',
'tests'
));
$input = new ArgvInput();
$output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true);
Kernel::boot($testSuite, $input, $output);
});
(static function () use ($bootPest): void {
$getopt = getopt('', [
'status-file:',
'progress-file:',
'testresult-file:',
'teamcity-file:',
'testdox-file:',
'testdox-color',
'phpunit-argv:',
]);
$composerAutoloadFiles = [
dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'autoload.php',
dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php',
dirname(__DIR__).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php',
];
foreach ($composerAutoloadFiles as $file) {
if (file_exists($file)) {
require_once $file;
define('PHPUNIT_COMPOSER_INSTALL', $file);
break;
}
}
assert(isset($getopt['status-file']) && is_string($getopt['status-file']));
$statusFile = fopen($getopt['status-file'], 'wb');
assert(is_resource($statusFile));
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
assert(isset($getopt['phpunit-argv']) && is_string($getopt['phpunit-argv']));
$phpunitArgv = unserialize($getopt['phpunit-argv'], ['allowed_classes' => false]);
assert(is_array($phpunitArgv));
$bootPest();
$phpunitArgv = CallsHandleArguments::execute($phpunitArgv);
$application = new ApplicationForWrapperWorker(
$phpunitArgv,
$getopt['progress-file'],
$getopt['testresult-file'],
$getopt['teamcity-file'] ?? null,
$getopt['testdox-file'] ?? null,
isset($getopt['testdox-color']),
);
while (true) {
if (feof(STDIN)) {
$application->end();
exit;
}
$testPath = fgets(STDIN);
if ($testPath === false || $testPath === WrapperWorker::COMMAND_EXIT) {
$application->end();
exit;
}
$exitCode = $application->runTest(trim($testPath));
fwrite($statusFile, (string) $exitCode);
fflush($statusFile);
}
})();

View File

@ -17,10 +17,17 @@
} }
], ],
"require": { "require": {
"php": "^7.3 || ^8.0", "php": "^8.1.0",
"nunomaduro/collision": "^5.4.0", "brianium/paratest": "^7.1.2",
"pestphp/pest-plugin": "^1.0.0", "nunomaduro/collision": "^7.3.2",
"phpunit/phpunit": "^9.5.5" "nunomaduro/termwind": "^1.15.1",
"pestphp/pest-plugin": "^2.0.0",
"pestphp/pest-plugin-arch": "^2.0.1",
"phpunit/phpunit": "^10.0.17"
},
"conflict": {
"webmozart/assert": "<1.11.0",
"phpunit/phpunit": ">10.0.17"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -33,6 +40,7 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Tests\\Fixtures\\Covers\\": "tests/Fixtures/Covers",
"Tests\\": "tests/PHPUnit/" "Tests\\": "tests/PHPUnit/"
}, },
"files": [ "files": [
@ -40,30 +48,33 @@
] ]
}, },
"require-dev": { "require-dev": {
"illuminate/console": "^8.47.0", "pestphp/pest-dev-tools": "^2.5.0",
"illuminate/support": "^8.47.0", "symfony/process": "^6.2.7"
"laravel/dusk": "^6.15.0",
"pestphp/pest-dev-tools": "dev-master",
"pestphp/pest-plugin-parallel": "^1.0"
}, },
"minimum-stability": "dev", "minimum-stability": "stable",
"prefer-stable": true,
"config": { "config": {
"sort-packages": true, "sort-packages": true,
"preferred-install": "dist" "preferred-install": "dist",
"allow-plugins": {
"pestphp/pest-plugin": true
}
}, },
"bin": [ "bin": [
"bin/pest" "bin/pest"
], ],
"scripts": { "scripts": {
"lint": "php-cs-fixer fix -v", "refacto": "rector",
"test:lint": "php-cs-fixer fix -v --dry-run", "lint": "pint",
"test:types": "phpstan analyse --ansi --memory-limit=-1", "test:refacto": "rector --dry-run",
"test:unit": "php bin/pest --colors=always --exclude-group=integration", "test:lint": "pint --test",
"test:parallel": "php bin/pest -p --colors=always --exclude-group=integration", "test:types": "phpstan analyse --ansi --memory-limit=-1 --debug",
"test:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
"test:inline": "php bin/pest --colors=always --configuration=phpunit.inline.xml",
"test:parallel": "php bin/pest --colors=always --exclude-group=integration --parallel --processes=10",
"test:integration": "php bin/pest --colors=always --group=integration", "test:integration": "php bin/pest --colors=always --group=integration",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always", "update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always",
"test": [ "test": [
"@test:refacto",
"@test:lint", "@test:lint",
"@test:types", "@test:types",
"@test:unit", "@test:unit",
@ -72,19 +83,22 @@
] ]
}, },
"extra": { "extra": {
"branch-alias": {
"dev-master": "1.x-dev"
},
"pest": { "pest": {
"plugins": [ "plugins": [
"Pest\\Plugins\\Bail",
"Pest\\Plugins\\Cache",
"Pest\\Plugins\\Coverage", "Pest\\Plugins\\Coverage",
"Pest\\Plugins\\Init", "Pest\\Plugins\\Init",
"Pest\\Plugins\\Version" "Pest\\Plugins\\Environment",
] "Pest\\Plugins\\Help",
}, "Pest\\Plugins\\Memory",
"laravel": { "Pest\\Plugins\\Only",
"providers": [ "Pest\\Plugins\\Printer",
"Pest\\Laravel\\PestServiceProvider" "Pest\\Plugins\\ProcessIsolation",
"Pest\\Plugins\\Profile",
"Pest\\Plugins\\Retry",
"Pest\\Plugins\\Version",
"Pest\\Plugins\\Parallel"
] ]
} }
} }

14
docker-compose.yml Normal file
View File

@ -0,0 +1,14 @@
version: "3.8"
services:
php:
build:
context: ./docker
volumes:
- .:/var/www/html
composer:
build:
context: ./docker
volumes:
- .:/var/www/html
entrypoint: ["composer"]

23
docker/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
ARG PHP=8.1
FROM php:${PHP}-cli-alpine
RUN apk update \
&& apk add zip libzip-dev icu-dev
RUN docker-php-ext-configure zip
RUN docker-php-ext-install zip
RUN docker-php-ext-enable zip
RUN docker-php-ext-configure intl
RUN docker-php-ext-install intl
RUN docker-php-ext-enable intl
RUN apk add --no-cache $PHPIZE_DEPS linux-headers
RUN pecl install xdebug
RUN docker-php-ext-enable xdebug
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
ENTRYPOINT ["php"]

View File

@ -0,0 +1,171 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace PHPUnit\Runner\Filter;
use function end;
use Exception;
use function implode;
use Pest\Contracts\HasPrintableTestCaseName;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use function preg_match;
use RecursiveFilterIterator;
use RecursiveIterator;
use function sprintf;
use function str_replace;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class NameFilterIterator extends RecursiveFilterIterator
{
private ?string $filter = null;
private ?int $filterMin = null;
private ?int $filterMax = null;
/**
* @throws Exception
*/
public function __construct(RecursiveIterator $iterator, string $filter)
{
parent::__construct($iterator);
$this->setFilter($filter);
}
public function accept(): bool
{
$test = $this->getInnerIterator()->current();
if ($test instanceof TestSuite) {
return true;
}
$tmp = $this->describe($test);
if ($tmp[0] !== '') {
$name = implode('::', $tmp);
} else {
$name = $tmp[1];
}
$accepted = @preg_match($this->filter, $name, $matches);
if ($accepted && isset($this->filterMax)) {
$set = end($matches);
$accepted = $set >= $this->filterMin && $set <= $this->filterMax;
}
return (bool) $accepted;
}
/**
* @throws Exception
*/
private function setFilter(string $filter): void
{
if (@preg_match($filter, '') === false) {
// Handles:
// * testAssertEqualsSucceeds#4
// * testAssertEqualsSucceeds#4-8
if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
if (isset($matches[3]) && $matches[2] < $matches[3]) {
$filter = sprintf(
'%s.*with data set #(\d+)$',
$matches[1]
);
$this->filterMin = (int) $matches[2];
$this->filterMax = (int) $matches[3];
} else {
$filter = sprintf(
'%s.*with data set #%s$',
$matches[1],
$matches[2]
);
}
} // Handles:
// * testDetermineJsonError@JSON_ERROR_NONE
// * testDetermineJsonError@JSON.*
elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
$filter = sprintf(
'%s.*with data set "%s"$',
$matches[1],
$matches[2]
);
}
// Escape delimiters in regular expression. Do NOT use preg_quote,
// to keep magic characters.
$filter = sprintf(
'/%s/i',
str_replace(
'/',
'\\/',
$filter
)
);
}
$this->filter = $filter;
}
/**
* @psalm-return array{0: string, 1: string}
*/
private function describe(Test $test): array
{
if ($test instanceof HasPrintableTestCaseName) {
return [
$test::getPrintableTestCaseName(),
$test->getPrintableTestCaseMethodName(),
];
}
if ($test instanceof TestCase) {
return [$test::class, $test->nameWithDataSet()];
}
if ($test instanceof SelfDescribing) {
return ['', $test->toString()];
}
return ['', $test::class];
}
}

View File

@ -0,0 +1,195 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Runner\ResultCache;
use function array_keys;
use function assert;
use const DIRECTORY_SEPARATOR;
use function dirname;
use function file_get_contents;
use function file_put_contents;
use function is_array;
use function is_dir;
use function is_file;
use function json_decode;
use function json_encode;
use function Pest\version;
use PHPUnit\Framework\TestStatus\TestStatus;
use PHPUnit\Runner\DirectoryCannotBeCreatedException;
use PHPUnit\Runner\Exception;
use PHPUnit\Util\Filesystem;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class DefaultResultCache implements ResultCache
{
/**
* @var string
*/
private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache';
private readonly string $cacheFilename;
/**
* @psalm-var array<string, TestStatus>
*/
private array $defects = [];
/**
* @psalm-var array<string, TestStatus>
*/
private array $currentDefects = [];
/**
* @psalm-var array<string, float>
*/
private array $times = [];
public function __construct(?string $filepath = null)
{
if ($filepath !== null && is_dir($filepath)) {
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_RESULT_CACHE_FILENAME;
}
$this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME;
}
public function setStatus(string $id, TestStatus $status): void
{
if ($status->isFailure() || $status->isError()) {
$this->currentDefects[$id] = $status;
$this->defects[$id] = $status;
}
}
public function status(string $id): TestStatus
{
return $this->defects[$id] ?? TestStatus::unknown();
}
public function setTime(string $id, float $time): void
{
if (! isset($this->currentDefects[$id])) {
unset($this->defects[$id]);
}
$this->times[$id] = $time;
}
public function time(string $id): float
{
return $this->times[$id] ?? 0.0;
}
public function load(): void
{
if (! is_file($this->cacheFilename)) {
return;
}
$data = json_decode(
file_get_contents($this->cacheFilename),
true
);
if ($data === null) {
return;
}
if (! isset($data['version'])) {
return;
}
if ($data['version'] !== $this->cacheVersion()) {
return;
}
assert(isset($data['defects']) && is_array($data['defects']));
assert(isset($data['times']) && is_array($data['times']));
foreach (array_keys($data['defects']) as $test) {
$data['defects'][$test] = TestStatus::from($data['defects'][$test]);
}
$this->defects = $data['defects'];
$this->times = $data['times'];
}
/**
* @throws Exception
*/
public function persist(): void
{
if (! Filesystem::createDirectory(dirname($this->cacheFilename))) {
throw new DirectoryCannotBeCreatedException($this->cacheFilename);
}
$data = [
'version' => $this->cacheVersion(),
'defects' => [],
'times' => $this->times,
];
foreach ($this->defects as $test => $status) {
$data['defects'][$test] = $status->asInt();
}
file_put_contents(
$this->cacheFilename,
json_encode($data),
LOCK_EX
);
}
/**
* Returns the cache version.
*/
private function cacheVersion(): string
{
return 'pest_'.version();
}
}

View File

@ -0,0 +1,204 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
declare(strict_types=1);
namespace PHPUnit\Runner;
use function array_diff;
use function array_values;
use function basename;
use function class_exists;
use Exception;
use function get_declared_classes;
use Pest\Contracts\HasPrintableTestCaseName;
use Pest\TestCases\IgnorableTestCase;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionException;
use function substr;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class TestSuiteLoader
{
/**
* @psalm-var list<class-string>
*/
private static array $loadedClasses = [];
/**
* @psalm-var list<class-string>
*/
private static array $declaredClasses = [];
public function __construct()
{
if (empty(self::$declaredClasses)) {
self::$declaredClasses = get_declared_classes();
}
}
/**
* @throws Exception
*/
public function load(string $suiteClassFile): ReflectionClass
{
$suiteClassName = $this->classNameFromFileName($suiteClassFile);
(static function () use ($suiteClassFile) {
include_once $suiteClassFile;
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
})();
$loadedClasses = array_values(
array_diff(
get_declared_classes(),
array_merge(
self::$declaredClasses,
self::$loadedClasses
)
)
);
self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses);
if (empty($loadedClasses)) {
return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
$testCaseFound = false;
$class = false;
foreach (array_reverse($loadedClasses) as $loadedClass) {
if (
is_subclass_of($loadedClass, HasPrintableTestCaseName::class)
|| is_subclass_of($loadedClass, TestCase::class)) {
try {
$class = new ReflectionClass($loadedClass);
// @codeCoverageIgnoreStart
} catch (ReflectionException) {
continue;
}
if ($class->isAbstract() || ($class->getFileName() !== $suiteClassFile)) {
if (! str_contains($class->getFileName(), 'TestCaseFactory.php')) {
continue;
}
}
$suiteClassName = $loadedClass;
$testCaseFound = true;
break;
}
}
if (! $testCaseFound) {
foreach (array_reverse($loadedClasses) as $loadedClass) {
$offset = 0 - strlen($suiteClassName);
if (stripos(substr($loadedClass, $offset - 1), '\\'.$suiteClassName) === 0 ||
stripos(substr($loadedClass, $offset - 1), '_'.$suiteClassName) === 0) {
try {
$class = new ReflectionClass($loadedClass);
// @codeCoverageIgnoreStart
} catch (ReflectionException) {
continue;
}
$suiteClassName = $loadedClass;
$testCaseFound = true;
break;
}
}
}
if (! $testCaseFound) {
return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
if (! class_exists($suiteClassName, false)) {
return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
// @codeCoverageIgnoreEnd
if ($class->isSubclassOf(TestCase::class) && ! $class->isAbstract()) {
return $class;
}
if ($class->hasMethod('suite')) {
try {
$method = $class->getMethod('suite');
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
}
// @codeCoverageIgnoreEnd
if (! $method->isAbstract() && $method->isPublic() && $method->isStatic()) {
return $class;
}
}
return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
public function reload(ReflectionClass $aClass): ReflectionClass
{
return $aClass;
}
private function classNameFromFileName(string $suiteClassFile): string
{
$className = basename($suiteClassFile, '.php');
$dotPos = strpos($className, '.');
if ($dotPos !== false) {
$className = substr($className, 0, $dotPos);
}
return $className;
}
private function exceptionFor(string $className, string $filename): ReflectionClass
{
return new ReflectionClass(IgnorableTestCase::class);
}
}

View File

@ -0,0 +1,104 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI\Command;
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
use PHPUnit\TextUI\Configuration\Configuration;
use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException;
use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer;
use SebastianBergmann\Timer\NoActiveTimerException;
use SebastianBergmann\Timer\Timer;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class WarmCodeCoverageCacheCommand implements Command
{
private readonly Configuration $configuration;
private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry)
{
$this->configuration = $configuration;
$this->codeCoverageFilterRegistry = $codeCoverageFilterRegistry;
}
/**
* @throws NoActiveTimerException
* @throws NoCoverageCacheDirectoryException
*/
public function execute(): Result
{
if (! $this->configuration->hasCoverageCacheDirectory()) {
return Result::from(
'Cache for static analysis has not been configured'.PHP_EOL,
Result::FAILURE
);
}
$this->codeCoverageFilterRegistry->init($this->configuration);
if (! $this->codeCoverageFilterRegistry->configured()) {
return Result::from(
'Filter for code coverage has not been configured'.PHP_EOL,
Result::FAILURE
);
}
$timer = new Timer;
$timer->start();
(new CacheWarmer)->warmCache(
$this->configuration->coverageCacheDirectory(),
! $this->configuration->disableCodeCoverageIgnore(),
$this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(),
$this->codeCoverageFilterRegistry->get()
);
return Result::from();
}
}

View File

@ -0,0 +1,80 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI\Output\Default\ProgressPrinter;
use PHPUnit\Event\Test\Skipped;
use PHPUnit\Event\Test\SkippedSubscriber;
use ReflectionClass;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*
* This file is overridden to allow Pest Parallel to show todo items in the progress output.
*/
final class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
{
/**
* Notifies the printer that a test was skipped.
*/
public function notify(Skipped $event): void
{
if (str_contains($event->message(), '__TODO__')) {
$this->printTodoItem();
}
$this->printer()->testSkipped();
}
/**
* Prints a "T" to the standard PHPUnit output to indicate a todo item.
*/
private function printTodoItem(): void
{
$mirror = new ReflectionClass($this->printer());
$printerMirror = $mirror->getMethod('printProgress');
$printerMirror->invoke($this->printer(), 'T');
}
}

View File

@ -0,0 +1,128 @@
<?php
/*
* BSD 3-Clause License
*
* Copyright (c) 2001-2023, Sebastian Bergmann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use function array_map;
use Pest\Plugins\Only;
use PHPUnit\Event;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\Filter\Factory;
use PHPUnit\TextUI\Configuration\Configuration;
use PHPUnit\TextUI\Configuration\FilterNotConfiguredException;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class TestSuiteFilterProcessor
{
private Factory $filterFactory;
public function __construct(Factory $factory = new Factory)
{
$this->filterFactory = $factory;
}
/**
* @throws Event\RuntimeException
* @throws FilterNotConfiguredException
*/
public function process(Configuration $configuration, TestSuite $suite): void
{
if (! $configuration->hasFilter() &&
! $configuration->hasGroups() &&
! $configuration->hasExcludeGroups() &&
! $configuration->hasTestsCovering() &&
! $configuration->hasTestsUsing() &&
! Only::isEnabled()
) {
return;
}
if ($configuration->hasExcludeGroups()) {
$this->filterFactory->addExcludeGroupFilter(
$configuration->excludeGroups()
);
}
if (Only::isEnabled()) {
$this->filterFactory->addIncludeGroupFilter(['__pest_only']);
} elseif ($configuration->hasGroups()) {
$this->filterFactory->addIncludeGroupFilter(
$configuration->groups()
);
}
if ($configuration->hasTestsCovering()) {
$this->filterFactory->addIncludeGroupFilter(
array_map(
static fn (string $name): string => '__phpunit_covers_'.$name,
$configuration->testsCovering()
)
);
}
if ($configuration->hasTestsUsing()) {
$this->filterFactory->addIncludeGroupFilter(
array_map(
static fn (string $name): string => '__phpunit_uses_'.$name,
$configuration->testsUsing()
)
);
}
if ($configuration->hasFilter()) {
$this->filterFactory->addNameFilter(
$configuration->filter()
);
}
$suite->injectFilter($this->filterFactory);
Event\Facade::emitter()->testSuiteFiltered(
Event\TestSuite\TestSuiteBuilder::from($suite)
);
}
}

View File

@ -9,26 +9,15 @@ parameters:
- src - src
checkMissingIterableValueType: true checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: false
reportUnmatchedIgnoredErrors: true reportUnmatchedIgnoredErrors: true
ignoreErrors: ignoreErrors:
- "#type mixed is not subtype of native#" - "#Language construct isset\\(\\) should not be used.#"
- "#is not allowed to extend#" - "#is not allowed to extend#"
- "#Language construct eval#" - "#is concrete, but does not have a Test suffix#"
- "#with a nullable type declaration#"
- "#type mixed is not subtype of native#"
- "# with null as default value#" - "# with null as default value#"
- "#has parameter \\$closure with default value.#" - "#has parameter \\$closure with default value.#"
- "#has parameter \\$description with default value.#" - "#has parameter \\$description with default value.#"
- "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#" - "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#"
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
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
-
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getPrintableTestCaseName\(\)#'
path: src/Logging

View File

@ -1,16 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
backupGlobals="false"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
bootstrap="vendor/autoload.php"
colors="true" colors="true"
failOnRisky="true"
failOnWarning="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
backupStaticProperties="false"
> >
<testsuites> <testsuites>
<testsuite name="default"> <testsuite name="default">
<directory suffix=".php">./tests</directory> <directory suffix=".php">./tests</directory>
</testsuite> <exclude>./tests/.snapshots</exclude>
</testsuites> <exclude>./tests/.tests</exclude>
<coverage processUncoveredFiles="true"> </testsuite>
<include> </testsuites>
<directory suffix=".php">./src</directory> <coverage>
</include> <include>
</coverage> <directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit> </phpunit>

31
rector.php Normal file
View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__.'/src',
]);
$rectorConfig->rules([
InlineConstructorDefaultToPropertyRector::class,
]);
$rectorConfig->skip([
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
]);
$rectorConfig->sets([
LevelSetList::UP_TO_PHP_81,
SetList::CODE_QUALITY,
SetList::DEAD_CODE,
SetList::EARLY_RETURN,
SetList::TYPE_DECLARATION,
SetList::PRIVATIZATION,
]);
};

View File

@ -0,0 +1,17 @@
<?php
/** @var string $type */
/** @var string $content */
[$bgBadgeColor, $bgBadgeText] = match ($type) {
'INFO' => ['blue', 'INFO'],
'ERROR' => ['red', 'ERROR'],
};
?>
<div class="my-1">
<span class="ml-2 px-1 bg-<?php echo $bgBadgeColor ?>-600 font-bold"><?php echo htmlspecialchars($bgBadgeText) ?></span>
<span class="ml-1">
<?php echo htmlspecialchars($content) ?>
</span>
</div>

View File

@ -0,0 +1 @@
<div></div>

View File

@ -0,0 +1,12 @@
<div class="flex mx-2 max-w-150">
<span>
<?php echo htmlspecialchars($left) ?>
</span>
<span class="flex-1 content-repeat-[.] text-gray ml-1"></span>
<?php if ($right !== '') { ?>
<span class="ml-1 text-gray">
<?php echo htmlspecialchars($right) ?>
</span>
<?php } ?>
</div>

View File

@ -0,0 +1,4 @@
<div class="mx-2">
<span class="text-yellow font-bold">USAGE:</span><span class="ml-1">pest</span><span class="ml-1 text-gray"><?php echo htmlspecialchars('<file>') ?> [options]</span>
</div>

View File

@ -0,0 +1,3 @@
<div class="my-1 mx-2">
<span>Pest Testing Framework</span><span class="ml-1 text-blue font-bold"><?php echo htmlspecialchars($version) ?></span><span>.</span>
</div>

View File

@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Actions;
use NunoMaduro\Collision\Adapters\Phpunit\Printer;
use Pest\Logging\JUnit;
use Pest\Logging\TeamCity;
use PHPUnit\TextUI\DefaultResultPrinter;
/**
* @internal
*/
final class AddsDefaults
{
private const PRINTER = 'printer';
/**
* Adds default arguments to the given `arguments` array.
*
* @param array<string, mixed> $arguments
*
* @return array<string, mixed>
*/
public static function to(array $arguments): array
{
if (!array_key_exists(self::PRINTER, $arguments)) {
$arguments[self::PRINTER] = new Printer(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
}
if ($arguments[self::PRINTER] === \PHPUnit\Util\Log\TeamCity::class) {
$arguments[self::PRINTER] = new TeamCity(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
}
// Load our junit logger instead.
if (array_key_exists('junitLogfile', $arguments)) {
$arguments['listeners'][] = new JUnit(
$arguments['junitLogfile']
);
unset($arguments['junitLogfile']);
}
return $arguments;
}
}

View File

@ -1,64 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Actions;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\WarningTestCase;
/**
* @internal
*/
final class AddsTests
{
/**
* Adds tests to the given test suite.
*
* @param TestSuite<\PHPUnit\Framework\TestCase> $testSuite
*/
public static function to(TestSuite $testSuite, \Pest\TestSuite $pestTestSuite): void
{
self::removeTestClosureWarnings($testSuite);
$testSuites = [];
$pestTestSuite->tests->build($pestTestSuite, function (TestCase $testCase) use (&$testSuites): void {
$testCaseClass = get_class($testCase);
if (!array_key_exists($testCaseClass, $testSuites)) {
$testSuites[$testCaseClass] = [];
}
$testSuites[$testCaseClass][] = $testCase;
});
foreach ($testSuites as $testCaseName => $testCases) {
$testTestSuite = new TestSuite($testCaseName);
$testTestSuite->setTests([]);
foreach ($testCases as $testCase) {
$testTestSuite->addTest($testCase, $testCase->getGroups());
}
$testSuite->addTestSuite($testTestSuite);
}
}
/**
* @param TestSuite<\PHPUnit\Framework\TestCase> $testSuite
*/
private static function removeTestClosureWarnings(TestSuite $testSuite): void
{
$tests = $testSuite->tests();
foreach ($tests as $key => $test) {
if ($test instanceof TestSuite) {
self::removeTestClosureWarnings($test);
}
if ($test instanceof WarningTestCase) {
unset($tests[$key]);
}
}
$testSuite->setTests($tests);
}
}

View File

@ -1,50 +0,0 @@
<?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;
}
}

View File

@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Actions;
use Pest\Support\Str;
use function Pest\testDirectory;
use PHPUnit\Util\FileLoader;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
/**
* @internal
*/
final class LoadStructure
{
/**
* The Pest convention.
*
* @var array<int, string>
*/
private const STRUCTURE = [
'Expectations.php',
'Datasets.php',
'Helpers.php',
'Pest.php',
'Datasets',
];
/**
* Validates the configuration in the given `configuration`.
*/
public static function in(string $rootPath): void
{
$testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory();
$load = function ($filename): bool {
return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename);
};
foreach (self::STRUCTURE as $filename) {
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
if (!file_exists($filename)) {
continue;
}
if (is_dir($filename)) {
$directory = new RecursiveDirectoryIterator($filename);
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $file) {
$filename = $file->__toString();
if (Str::endsWith($filename, '.php') && file_exists($filename)) {
require_once $filename;
}
}
} else {
$load($filename);
}
}
}
}

View File

@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Actions;
use Pest\Exceptions\AttributeNotSupportedYet;
use Pest\Exceptions\FileOrFolderNotFound;
use PHPUnit\TextUI\XmlConfiguration\Loader;
/**
* @internal
*/
final class ValidatesConfiguration
{
/**
* @var string
*/
private const CONFIGURATION_KEY = 'configuration';
/**
* Validates the configuration in the given `configuration`.
*
* @param array<string, mixed> $arguments
*/
public static function in($arguments): void
{
if (!array_key_exists(self::CONFIGURATION_KEY, $arguments) || !file_exists($arguments[self::CONFIGURATION_KEY])) {
throw new FileOrFolderNotFound('phpunit.xml');
}
$configuration = (new Loader())->load($arguments[self::CONFIGURATION_KEY])->phpunit();
if ($configuration->processIsolation()) {
throw new AttributeNotSupportedYet('processIsolation', 'true');
}
}
}

View File

@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Actions;
use Pest\Exceptions\FileOrFolderNotFound;
use Pest\TestSuite;
/**
* @internal
*/
final class ValidatesEnvironment
{
/**
* The need files on the root path.
*
* @var array<int, string>
*/
private const NEEDED_FILES = [
'composer.json',
];
/**
* Validates the environment.
*/
public static function in(TestSuite $testSuite): void
{
$rootPath = $testSuite->rootPath;
$exists = function ($neededFile) use ($rootPath): bool {
return file_exists(sprintf('%s%s%s', $rootPath, DIRECTORY_SEPARATOR, $neededFile));
};
foreach (self::NEEDED_FILES as $neededFile) {
if (!$exists($neededFile)) {
throw new FileOrFolderNotFound($neededFile);
}
}
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use Pest\Support\DatasetInfo;
use Pest\Support\Str;
use function Pest\testDirectory;
use Pest\TestSuite;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
/**
* @internal
*/
final class BootFiles implements Bootstrapper
{
/**
* The structure of the tests directory.
*
* @var array<int, string>
*/
private const STRUCTURE = [
'Expectations',
'Expectations.php',
'Helpers',
'Helpers.php',
'Pest.php',
];
/**
* Boots the structure of the tests directory.
*/
public function boot(): void
{
$rootPath = TestSuite::getInstance()->rootPath;
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
foreach (self::STRUCTURE as $filename) {
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
if (! file_exists($filename)) {
continue;
}
if (is_dir($filename)) {
$directory = new RecursiveDirectoryIterator($filename);
$iterator = new RecursiveIteratorIterator($directory);
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
$this->load($file->__toString());
}
} else {
$this->load($filename);
}
}
$this->bootDatasets($testsPath);
}
/**
* Loads, if possible, the given file.
*/
private function load(string $filename): void
{
if (! Str::endsWith($filename, '.php')) {
return;
}
if (! file_exists($filename)) {
return;
}
include_once $filename;
}
private function bootDatasets(string $testsPath): void
{
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
foreach ($files as $file) {
if (DatasetInfo::isADatasetsFile($file) || DatasetInfo::isInsideADatasetsDirectory($file)) {
$this->load($file);
}
}
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use Pest\KernelDump;
use Pest\Support\Container;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BootKernelDump implements Bootstrapper
{
/**
* Creates a new Boot Kernel Dump instance.
*/
public function __construct(
private readonly OutputInterface $output,
) {
// ...
}
/**
* Boots the kernel dump.
*/
public function boot(): void
{
Container::getInstance()->add(KernelDump::class, $kernelDump = new KernelDump(
$this->output,
));
$kernelDump->enable();
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use Pest\Exceptions\ShouldNotHappen;
/**
* @internal
*/
final class BootOverrides implements Bootstrapper
{
/**
* The list of files to be overridden.
*
* @var array<int, string>
*/
private const FILES = [
'Runner/Filter/NameFilterIterator.php',
'Runner/ResultCache/DefaultResultCache.php',
'Runner/TestSuiteLoader.php',
'TextUI/Command/WarmCodeCoverageCacheCommand.php',
'TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php',
'TextUI/TestSuiteFilterProcessor.php',
];
/**
* Boots the list of files to be overridden.
*/
public function boot(): void
{
foreach (self::FILES as $file) {
$file = __DIR__."/../../overrides/$file";
if (! file_exists($file)) {
throw ShouldNotHappen::fromMessage(sprintf('File [%s] does not exist.', $file));
}
require_once $file;
}
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use Pest\Subscribers;
use Pest\Support\Container;
use PHPUnit\Event;
use PHPUnit\Event\Subscriber;
/**
* @internal
*/
final class BootSubscribers implements Bootstrapper
{
/**
* The list of Subscribers.
*
* @var array<int, class-string<Subscriber>>
*/
private const SUBSCRIBERS = [
Subscribers\EnsureConfigurationIsAvailable::class,
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
Subscribers\EnsureKernelDumpIsFlushed::class,
Subscribers\EnsureTeamCityEnabled::class,
];
/**
* Creates a new instance of the Boot Subscribers.
*/
public function __construct(
private readonly Container $container,
) {
}
/**
* Boots the list of Subscribers.
*/
public function boot(): void
{
foreach (self::SUBSCRIBERS as $subscriber) {
$instance = $this->container->get($subscriber);
assert($instance instanceof Subscriber);
Event\Facade::instance()->registerSubscriber($instance);
}
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use Pest\Support\View;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BootView implements Bootstrapper
{
/**
* Creates a new instance of the Boot View.
*/
public function __construct(
private readonly OutputInterface $output
) {
// ..
}
/**
* Boots the view renderer.
*/
public function boot(): void
{
View::renderUsing($this->output);
}
}

View File

@ -14,13 +14,12 @@ trait Expectable
/** /**
* @template TValue * @template TValue
* *
* Creates a new expectation. * Creates a new Expectation.
*
* @param TValue $value
* *
* @param TValue $value
* @return Expectation<TValue> * @return Expectation<TValue>
*/ */
public function expect($value): Expectation public function expect(mixed $value): Expectation
{ {
return new Expectation($value); return new Expectation($value);
} }

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Pest\Concerns; namespace Pest\Concerns;
use BadMethodCallException;
use Closure; use Closure;
/** /**
@ -13,42 +12,25 @@ use Closure;
trait Extendable trait Extendable
{ {
/** /**
* The list of extends.
*
* @var array<string, Closure> * @var array<string, Closure>
*/ */
private static $extends = []; private static array $extends = [];
/** /**
* Register a custom extend. * Register a new extend.
*/ */
public static function extend(string $name, Closure $extend): void public function extend(string $name, Closure $extend): void
{ {
static::$extends[$name] = $extend; static::$extends[$name] = $extend;
} }
/** /**
* Checks if extend is registered. * Checks if given extend name is registered.
*/ */
public static function hasExtend(string $name): bool public static function hasExtend(string $name): bool
{ {
return array_key_exists($name, static::$extends); return array_key_exists($name, static::$extends);
} }
/**
* Dynamically handle calls to the class.
*
* @param array<int, mixed> $parameters
*
* @return mixed
*/
public function __call(string $method, array $parameters)
{
if (!static::hasExtend($method)) {
throw new BadMethodCallException("$method is not a callable method name.");
}
/** @var Closure $extend */
$extend = static::$extends[$method]->bindTo($this, static::class);
return $extend(...$parameters);
}
} }

View File

@ -9,21 +9,33 @@ namespace Pest\Concerns\Logging;
*/ */
trait WritesToConsole trait WritesToConsole
{ {
/**
* Writes the given success message to the console.
*/
private function writeSuccess(string $message): void private function writeSuccess(string $message): void
{ {
$this->writePestTestOutput($message, 'fg-green, bold', '✓'); $this->writePestTestOutput($message, 'fg-green, bold', '✓');
} }
/**
* Writes the given error message to the console.
*/
private function writeError(string $message): void private function writeError(string $message): void
{ {
$this->writePestTestOutput($message, 'fg-red, bold', ''); $this->writePestTestOutput($message, 'fg-red, bold', '');
} }
/**
* Writes the given warning message to the console.
*/
private function writeWarning(string $message): void private function writeWarning(string $message): void
{ {
$this->writePestTestOutput($message, 'fg-yellow, bold', '-'); $this->writePestTestOutput($message, 'fg-yellow, bold', '-');
} }
/**
* Writes the give message to the console.
*/
private function writePestTestOutput(string $message, string $color, string $symbol): void private function writePestTestOutput(string $message, string $color, string $symbol): void
{ {
$this->writeWithColor($color, "$symbol ", false); $this->writeWithColor($color, "$symbol ", false);

72
src/Concerns/Pipeable.php Normal file
View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Pest\Concerns;
use Closure;
use Pest\Expectation;
/**
* @internal
*/
trait Pipeable
{
/**
* The list of pipes.
*
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
*/
private static array $pipes = [];
/**
* The list of interceptors.
*
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
*/
private static array $interceptors = [];
/**
* Register a pipe to be applied before an expectation is checked.
*/
public function pipe(string $name, Closure $pipe): void
{
self::$pipes[$name][] = $pipe;
}
/**
* Register an interceptor that should replace an existing expectation.
*
* @param string|Closure(mixed $value, mixed ...$arguments):bool $filter
*/
public function intercept(string $name, string|Closure $filter, Closure $handler): void
{
if (is_string($filter)) {
$filter = fn ($value): bool => $value instanceof $filter;
}
self::$interceptors[$name][] = $handler;
$this->pipe($name, function ($next, ...$arguments) use ($handler, $filter): void {
/* @phpstan-ignore-next-line */
if ($filter($this->value, ...$arguments)) {
// @phpstan-ignore-next-line
$handler->bindTo($this, $this::class)(...$arguments);
return;
}
$next();
});
}
/**
* Get th list of pipes by the given name.
*
* @return array<int, Closure>
*/
private function pipes(string $name, object $context, string $scope): array
{
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
}
}

View File

@ -7,19 +7,19 @@ namespace Pest\Concerns;
/** /**
* @internal * @internal
*/ */
trait RetrievesValues trait Retrievable
{ {
/** /**
* @template TRetrievableValue * @template TRetrievableValue
* *
* Safely retrieve the value at the given key from an object or array. * Safely retrieve the value at the given key from an object or array.
* @template TRetrievableValue
* *
* @param array<string, TRetrievableValue>|object $value * @param array<string, TRetrievableValue>|object $value
* @param TRetrievableValue|null $default * @param TRetrievableValue|null $default
*
* @return TRetrievableValue|null * @return TRetrievableValue|null
*/ */
private function retrieve(string $key, $value, $default = null) private function retrieve(string $key, mixed $value, mixed $default = null): mixed
{ {
if (is_array($value)) { if (is_array($value)) {
return $value[$key] ?? $default; return $value[$key] ?? $default;

View File

@ -5,162 +5,133 @@ declare(strict_types=1);
namespace Pest\Concerns; namespace Pest\Concerns;
use Closure; use Closure;
use Pest\Exceptions\DatasetArgsCountMismatch;
use Pest\Support\ChainableClosure; use Pest\Support\ChainableClosure;
use Pest\Support\ExceptionTrace; use Pest\Support\ExceptionTrace;
use Pest\Support\Reflection;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\ExecutionOrderDependency; use PHPUnit\Framework\TestCase;
use ReflectionException;
use ReflectionFunction;
use Throwable; use Throwable;
/** /**
* To avoid inheritance conflicts, all the fields related
* to Pest only will be prefixed by double underscore.
*
* @internal * @internal
*
* @mixin TestCase
*/ */
trait Testable trait Testable
{ {
/** /**
* The test case description. Contains the first * Test method description.
* argument of global functions like `it` and `test`.
*
* @var string
*/ */
private $__description; private string $__description;
/** /**
* Holds the test closure function. * Test "latest" method description.
*
* @var Closure
*/ */
private $__test; private static string $__latestDescription;
/** /**
* Holds a global/shared beforeEach ("set up") closure if one has been * The Test Case "test" closure.
* defined.
*
* @var Closure|null
*/ */
private $beforeEach = null; private Closure $__test;
/** /**
* Holds a global/shared afterEach ("tear down") closure if one has been * The Test Case "setUp" closure.
* defined.
*
* @var Closure|null
*/ */
private $afterEach = null; private ?Closure $__beforeEach = null;
/** /**
* Holds a global/shared beforeAll ("set up before") closure if one has been * The Test Case "tearDown" closure.
* defined.
*
* @var Closure|null
*/ */
private static $beforeAll = null; private ?Closure $__afterEach = null;
/** /**
* Holds a global/shared afterAll ("tear down after") closure if one has * The Test Case "setUpBeforeClass" closure.
* been defined.
*
* @var Closure|null
*/ */
private static $afterAll = null; private static ?Closure $__beforeAll = null;
/** /**
* Creates a new instance of the test case. * The test "tearDownAfterClass" closure.
*/ */
public function __construct(Closure $test, string $description, array $data) private static ?Closure $__afterAll = null;
/**
* Resets the test case static properties.
*/
public static function flush(): void
{ {
$this->__test = $test; self::$__beforeAll = null;
$this->__description = $description; self::$__afterAll = null;
self::$beforeAll = null;
self::$afterAll = null;
parent::__construct('__test', $data);
} }
/** /**
* Adds the groups to the current test case. * Creates a new Test Case instance.
*/ */
public function addGroups(array $groups): void public function __construct(string $name)
{ {
$groups = array_unique(array_merge($this->getGroups(), $groups)); parent::__construct($name);
$this->setGroups($groups); $test = TestSuite::getInstance()->tests->get(self::$__filename);
if ($test->hasMethod($name)) {
$method = $test->getMethod($name);
$this->__description = self::$__latestDescription = $method->description;
$this->__test = $method->getClosure($this);
}
} }
/** /**
* Add dependencies to the test case and map them to instances of ExecutionOrderDependency. * Adds a new "setUpBeforeClass" to the Test Case.
*/ */
public function addDependencies(array $tests): void public function __addBeforeAll(?Closure $hook): void
{ {
$className = get_class($this); if ($hook === null) {
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) {
$test = "{$className}::{$test}";
}
return new ExecutionOrderDependency($test, null, '');
}, $tests);
$this->setDependencies($tests);
}
/**
* Add a shared/"global" before all test hook that will execute **before**
* the test defined `beforeAll` hook(s).
*/
public function addBeforeAll(?Closure $hook): void
{
if (!$hook) {
return; return;
} }
self::$beforeAll = (self::$beforeAll instanceof Closure) self::$__beforeAll = (self::$__beforeAll instanceof Closure)
? ChainableClosure::fromStatic(self::$beforeAll, $hook) ? ChainableClosure::fromStatic(self::$__beforeAll, $hook)
: $hook; : $hook;
} }
/** /**
* Add a shared/"global" after all test hook that will execute **before** * Adds a new "tearDownAfterClass" to the Test Case.
* the test defined `afterAll` hook(s).
*/ */
public function addAfterAll(?Closure $hook): void public function __addAfterAll(?Closure $hook): void
{ {
if (!$hook) { if ($hook === null) {
return; return;
} }
self::$afterAll = (self::$afterAll instanceof Closure) self::$__afterAll = (self::$__afterAll instanceof Closure)
? ChainableClosure::fromStatic(self::$afterAll, $hook) ? ChainableClosure::fromStatic(self::$__afterAll, $hook)
: $hook; : $hook;
} }
/** /**
* Add a shared/"global" before each test hook that will execute **before** * Adds a new "setUp" to the Test Case.
* the test defined `beforeEach` hook.
*/ */
public function addBeforeEach(?Closure $hook): void public function __addBeforeEach(?Closure $hook): void
{ {
$this->addHook('beforeEach', $hook); $this->__addHook('__beforeEach', $hook);
} }
/** /**
* Add a shared/"global" after each test hook that will execute **before** * Adds a new "tearDown" to the Test Case.
* the test defined `afterEach` hook.
*/ */
public function addAfterEach(?Closure $hook): void public function __addAfterEach(?Closure $hook): void
{ {
$this->addHook('afterEach', $hook); $this->__addHook('__afterEach', $hook);
} }
/** /**
* Add a shared/global hook and compose them if more than one is passed. * Adds a new "hook" to the Test Case.
*/ */
private function addHook(string $property, ?Closure $hook): void private function __addHook(string $property, ?Closure $hook): void
{ {
if (!$hook) { if ($hook === null) {
return; return;
} }
@ -170,22 +141,7 @@ trait Testable
} }
/** /**
* Returns the test case name. Note that, in Pest * This method is called before the first test of this Test Case is run.
* we ignore withDataset argument as the description
* already contains the dataset description.
*/
public function getName(bool $withDataSet = true): string
{
return $this->__description;
}
public static function __getFileName(): string
{
return self::$__filename;
}
/**
* This method is called before the first test of this test class is run.
*/ */
public static function setUpBeforeClass(): void public static function setUpBeforeClass(): void
{ {
@ -193,22 +149,22 @@ trait Testable
$beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename); $beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename);
if (self::$beforeAll instanceof Closure) { if (self::$__beforeAll instanceof Closure) {
$beforeAll = ChainableClosure::fromStatic(self::$beforeAll, $beforeAll); $beforeAll = ChainableClosure::fromStatic(self::$__beforeAll, $beforeAll);
} }
call_user_func(Closure::bind($beforeAll, null, self::class)); call_user_func(Closure::bind($beforeAll, null, self::class));
} }
/** /**
* This method is called after the last test of this test class is run. * This method is called after the last test of this Test Case is run.
*/ */
public static function tearDownAfterClass(): void public static function tearDownAfterClass(): void
{ {
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename); $afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
if (self::$afterAll instanceof Closure) { if (self::$__afterAll instanceof Closure) {
$afterAll = ChainableClosure::fromStatic(self::$afterAll, $afterAll); $afterAll = ChainableClosure::fromStatic(self::$__afterAll, $afterAll);
} }
call_user_func(Closure::bind($afterAll, null, self::class)); call_user_func(Closure::bind($afterAll, null, self::class));
@ -217,7 +173,7 @@ trait Testable
} }
/** /**
* Gets executed before the test. * Gets executed before the Test Case.
*/ */
protected function setUp(): void protected function setUp(): void
{ {
@ -227,22 +183,22 @@ trait Testable
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename); $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
if ($this->beforeEach instanceof Closure) { if ($this->__beforeEach instanceof Closure) {
$beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach); $beforeEach = ChainableClosure::from($this->__beforeEach, $beforeEach);
} }
$this->__callClosure($beforeEach, func_get_args()); $this->__callClosure($beforeEach, func_get_args());
} }
/** /**
* Gets executed after the test. * Gets executed after the Test Case.
*/ */
protected function tearDown(): void protected function tearDown(): void
{ {
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
if ($this->afterEach instanceof Closure) { if ($this->__afterEach instanceof Closure) {
$afterEach = ChainableClosure::from($this->afterEach, $afterEach); $afterEach = ChainableClosure::from($this->__afterEach, $afterEach);
} }
$this->__callClosure($afterEach, func_get_args()); $this->__callClosure($afterEach, func_get_args());
@ -253,27 +209,16 @@ trait Testable
} }
/** /**
* Returns the test case as string. * Executes the Test Case current test.
*/
public function toString(): string
{
return \sprintf(
'%s::%s',
self::$__filename,
$this->__description
);
}
/**
* Runs the test.
*
* @return mixed
* *
* @throws Throwable * @throws Throwable
*/ */
public function __test() private function __runTest(Closure $closure, ...$args): mixed
{ {
return $this->__callClosure($this->__test, $this->resolveTestArguments(func_get_args())); $arguments = $this->__resolveTestArguments($args);
$this->__ensureDatasetArgumentNumberMatches($arguments);
return $this->__callClosure($closure, $arguments);
} }
/** /**
@ -281,27 +226,103 @@ trait Testable
* *
* @throws Throwable * @throws Throwable
*/ */
private function resolveTestArguments(array $arguments): array private function __resolveTestArguments(array $arguments): array
{ {
return array_map(function ($data) { $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
return $data instanceof Closure ? $this->__callClosure($data, []) : $data;
}, $arguments); $this->__description = self::$__latestDescription = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description;
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
if (count($arguments) !== 1) {
foreach ($arguments as $argumentIndex => $argumentValue) {
if (! $argumentValue instanceof Closure) {
continue;
}
if (in_array($testParameterTypes[$argumentIndex], [\Closure::class, 'callable', 'mixed'])) {
continue;
}
$arguments[$argumentIndex] = $this->__callClosure($argumentValue, []);
}
return $arguments;
}
if (! $arguments[0] instanceof Closure) {
return $arguments;
}
if (in_array($testParameterTypes[0], [\Closure::class, 'callable'])) {
return $arguments;
}
$boundDatasetResult = $this->__callClosure($arguments[0], []);
if (count($testParameterTypes) === 1) {
return [$boundDatasetResult];
}
if (! is_array($boundDatasetResult)) {
return [$boundDatasetResult];
}
return array_values($boundDatasetResult);
} }
/** /**
* @return mixed * Ensures dataset items count matches underlying test case required parameters
* *
* @throws Throwable * @throws ReflectionException
* @throws DatasetArgsCountMismatch
*/ */
private function __callClosure(Closure $closure, array $arguments) private function __ensureDatasetArgumentNumberMatches(array $arguments): void
{ {
return ExceptionTrace::ensure(function () use ($closure, $arguments) { if ($arguments === []) {
return call_user_func_array(Closure::bind($closure, $this, get_class($this)), $arguments); return;
}); }
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
$testReflection = new ReflectionFunction($underlyingTest);
$requiredParametersCount = $testReflection->getNumberOfRequiredParameters();
$suppliedParametersCount = count($arguments);
if ($suppliedParametersCount >= $requiredParametersCount) {
return;
}
throw new DatasetArgsCountMismatch($this->dataName(), $requiredParametersCount, $suppliedParametersCount);
} }
public function getPrintableTestCaseName(): string /**
* @throws Throwable
*/
private function __callClosure(Closure $closure, array $arguments): mixed
{ {
return ltrim(self::class, 'P\\'); return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
}
/**
* The printable test case name.
*/
public static function getPrintableTestCaseName(): string
{
return preg_replace('/P\\\/', '', self::class, 1);
}
/**
* The printable test case method name.
*/
public function getPrintableTestCaseMethodName(): string
{
return $this->__description;
}
/**
* The latest printable test case method name.
*/
public static function getLatestPrintableTestCaseMethodName(): string
{
return self::$__latestDescription;
} }
} }

View File

@ -1,132 +0,0 @@
<?php
declare(strict_types=1);
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\Plugins\Version;
use Pest\Support\Container;
use Pest\TestSuite;
use PHPUnit\Framework\TestSuite as BaseTestSuite;
use PHPUnit\TextUI\Command as BaseCommand;
use PHPUnit\TextUI\TestRunner;
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class Command extends BaseCommand
{
/**
* Holds the current testing suite.
*
* @var TestSuite
*/
private $testSuite;
/**
* Holds the current console output.
*
* @var OutputInterface
*/
private $output;
/**
* Creates a new instance of the command class.
*/
public function __construct(TestSuite $testSuite, OutputInterface $output)
{
$this->testSuite = $testSuite;
$this->output = $output;
}
/**
* {@inheritdoc}
*
* @phpstan-ignore-next-line
*
* @param array<int, string> $argv
*/
protected function handleArguments(array $argv): void
{
$argv = InteractsWithPlugins::handleArguments($argv);
parent::handleArguments($argv);
/*
* Let's validate the configuration. Making
* sure all options are yet supported by Pest.
*/
ValidatesConfiguration::in($this->arguments);
}
/**
* Creates a new PHPUnit test runner.
*/
protected function createRunner(): TestRunner
{
/*
* First, let's add the defaults we use on `pest`. Those
* are the printer class, and others that may be appear.
*/
$this->arguments = AddsDefaults::to($this->arguments);
$testRunner = new TestRunner($this->arguments['loader']);
$testSuite = $this->arguments['test'];
if (is_string($testSuite)) {
if (\is_dir($testSuite)) {
/** @var string[] $files */
$files = (new FileIteratorFacade())->getFilesAsArray(
$testSuite,
$this->arguments['testSuffixes']
);
} else {
$files = [$testSuite];
}
$testSuite = new BaseTestSuite($testSuite);
$testSuite->addTestFiles($files);
$this->arguments['test'] = $testSuite;
}
AddsTests::to($testSuite, $this->testSuite);
return $testRunner;
}
/**
* {@inheritdoc}
*
* @phpstan-ignore-next-line
*
* @param array<int, string> $argv
*/
public function run(array $argv, bool $exit = true): int
{
LoadStructure::in($this->testSuite->rootPath);
$result = parent::run($argv, false);
$result = InteractsWithPlugins::addOutput($result);
exit($result);
}
protected function showHelp(): void
{
/** @var Version $version */
$version = Container::getInstance()->get(Version::class);
$version->handleArguments(['--version']);
parent::showHelp();
(new Help($this->output))();
}
}

View File

@ -11,7 +11,11 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
final class Help final class Help
{ {
/** @var array<int, string> */ /**
* The Command messages.
*
* @var array<int, string>
*/
private const HELP_MESSAGES = [ private const HELP_MESSAGES = [
'<comment>Pest Options:</comment>', '<comment>Pest Options:</comment>',
' <info>--init</info> Initialise a standard Pest configuration', ' <info>--init</info> Initialise a standard Pest configuration',
@ -20,14 +24,17 @@ final class Help
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)', ' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
]; ];
/** @var OutputInterface */ /**
private $output; * Creates a new Console Command instance.
*/
public function __construct(OutputInterface $output) public function __construct(private readonly OutputInterface $output)
{ {
$this->output = $output; // ..
} }
/**
* Executes the Console Command.
*/
public function __invoke(): void public function __invoke(): void
{ {
foreach (self::HELP_MESSAGES as $message) { foreach (self::HELP_MESSAGES as $message) {

View File

@ -4,8 +4,11 @@ declare(strict_types=1);
namespace Pest\Console; namespace Pest\Console;
use Pest\Bootstrappers\BootView;
use Pest\Support\View;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion;
@ -14,38 +17,58 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
*/ */
final class Thanks final class Thanks
{ {
/** @var array<int, string> */ /**
* The support options.
*
* @var array<string, string>
*/
private const FUNDING_MESSAGES = [ private const FUNDING_MESSAGES = [
'', 'Star the project on GitHub' => 'https://github.com/pestphp/pest',
' - Star or contribute to Pest:', 'Tweet about the project' => 'https://twitter.com/pestphp',
' <options=bold>https://github.com/pestphp/pest</>', 'Sponsor the project' => 'https://github.com/sponsors/nunomaduro',
' - Tweet something about Pest on Twitter:',
' <options=bold>https://twitter.com/pestphp</>',
' - Sponsor the creator:',
' <options=bold>https://github.com/sponsors/nunomaduro</>',
]; ];
/** @var OutputInterface */ /**
private $output; * Creates a new Console Command instance.
*/
public function __construct(OutputInterface $output) public function __construct(
{ private readonly InputInterface $input,
$this->output = $output; private readonly OutputInterface $output
) {
// ..
} }
/** /**
* Asks the user to support Pest. * Executes the Console Command.
*/ */
public function __invoke(): void public function __invoke(): void
{ {
$wantsToSupport = (new SymfonyQuestionHelper())->ask( $bootstrapper = new BootView($this->output);
new ArrayInput([]), $bootstrapper->boot();
$this->output,
new ConfirmationQuestion( $wantsToSupport = false;
'Can you quickly <options=bold>star our GitHub repository</>? 🙏🏻',
true, if (getenv('PEST_NO_SUPPORT') !== 'true' && $this->input->isInteractive()) {
) $wantsToSupport = (new SymfonyQuestionHelper())->ask(
); new ArrayInput([]),
$this->output,
new ConfirmationQuestion(
' <options=bold>Wanna show Pest some love by starring it on GitHub?</>',
false,
)
);
View::render('components.new-line');
foreach (self::FUNDING_MESSAGES as $message => $link) {
View::render('components.two-column-detail', [
'left' => $message,
'right' => $link,
]);
}
View::render('components.new-line');
}
if ($wantsToSupport === true) { if ($wantsToSupport === true) {
if (PHP_OS_FAMILY == 'Darwin') { if (PHP_OS_FAMILY == 'Darwin') {
@ -58,9 +81,5 @@ final class Thanks
exec('xdg-open https://github.com/pestphp/pest'); exec('xdg-open https://github.com/pestphp/pest');
} }
} }
foreach (self::FUNDING_MESSAGES as $message) {
$this->output->writeln($message);
}
} }
} }

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
use Pest\Factories\TestCaseMethodFactory;
/**
* @interal
*/
interface AddsAnnotations
{
/**
* Adds annotations to the given test case method.
*
* @param array<int, string> $annotations
* @return array<int, string>
*/
public function __invoke(TestCaseMethodFactory $method, array $annotations): array;
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
/**
* @internal
*/
interface Bootstrapper
{
/**
* Boots the bootstrapper.
*/
public function boot(): void;
}

View File

@ -4,18 +4,12 @@ declare(strict_types=1);
namespace Pest\Contracts; namespace Pest\Contracts;
if (interface_exists(\NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName::class)) { use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName as BaseHasPrintableTestCaseName;
/**
* @internal /**
*/ * @internal
interface HasPrintableTestCaseName extends \NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName */
{ interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName
} {
} else { // ..
/**
* @internal
*/
interface HasPrintableTestCaseName
{
}
} }

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
interface Panicable
{
/**
* Renders the panic on the given output.
*/
public function render(OutputInterface $output): void;
/**
* The exit code to be used.
*/
public function exitCode(): int;
}

View File

@ -10,7 +10,7 @@ namespace Pest\Contracts\Plugins;
interface AddsOutput interface AddsOutput
{ {
/** /**
* Allows to add custom output after the test suite was executed. * Adds output after the Test Suite execution.
*/ */
public function addOutput(int $testReturnCode): int; public function addOutput(int $exitCode): int;
} }

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Bootable
{
/**
* Boots the plugin.
*/
public function boot(): void;
}

View File

@ -10,14 +10,10 @@ namespace Pest\Contracts\Plugins;
interface HandlesArguments interface HandlesArguments
{ {
/** /**
* Allows to handle custom command line arguments. * Adds arguments before of the Test Suite execution.
* *
* PLEASE NOTE: it is necessary to remove any custom argument from the array * @param array<int, string> $arguments
* because otherwise the application will complain about them * @return array<int, string>
*
* @param array<int, string> $arguments
*
* @return array<int, string> the updated list of arguments
*/ */
public function handleArguments(array $arguments): array; public function handleArguments(array $arguments): array;
} }

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Shutdownable
{
/**
* Shutdowns the plugin.
*/
public function shutdown(): void;
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
interface TestCaseFilter
{
/**
* Whether the test case is accepted.
*/
public function accept(string $testCaseFilename): bool;
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
use Pest\Factories\TestCaseMethodFactory;
interface TestCaseMethodFilter
{
/**
* Whether the test case method is accepted.
*/
public function accept(TestCaseMethodFactory $factory): bool;
}

View File

@ -1,177 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest;
use Closure;
use Pest\Exceptions\DatasetAlreadyExist;
use Pest\Exceptions\DatasetDoesNotExist;
use SebastianBergmann\Exporter\Exporter;
use Traversable;
/**
* @internal
*/
final class Datasets
{
/**
* Holds the datasets.
*
* @var array<int|string, Closure|iterable<int|string, mixed>>
*/
private static $datasets = [];
/**
* Sets the given.
*
* @param Closure|iterable<int|string, mixed> $data
*/
public static function set(string $name, $data): void
{
if (array_key_exists($name, self::$datasets)) {
throw new DatasetAlreadyExist($name);
}
self::$datasets[$name] = $data;
}
/**
* @return Closure|iterable<int|string, mixed>
*/
public static function get(string $name)
{
if (!array_key_exists($name, self::$datasets)) {
throw new DatasetDoesNotExist($name);
}
return self::$datasets[$name];
}
/**
* Resolves the current dataset to an array value.
*
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
*
* @return array<string, mixed>
*/
public static function resolve(string $description, array $datasets): array
{
/* @phpstan-ignore-next-line */
if (empty($datasets)) {
return [$description => []];
}
$datasets = self::processDatasets($datasets);
$datasetCombinations = self::getDataSetsCombinations($datasets);
$dataSetDescriptions = [];
$dataSetValues = [];
foreach ($datasetCombinations as $datasetCombination) {
$partialDescriptions = [];
$values = [];
foreach ($datasetCombination as $dataset_data) {
$partialDescriptions[] = $dataset_data['label'];
$values = array_merge($values, $dataset_data['values']);
}
$dataSetDescriptions[] = $description . ' with ' . implode(' / ', $partialDescriptions);
$dataSetValues[] = $values;
}
foreach (array_count_values($dataSetDescriptions) as $descriptionToCheck => $count) {
if ($count > 1) {
$index = 1;
foreach ($dataSetDescriptions as $i => $dataSetDescription) {
if ($dataSetDescription === $descriptionToCheck) {
$dataSetDescriptions[$i] .= sprintf(' #%d', $index++);
}
}
}
}
$namedData = [];
foreach ($dataSetDescriptions as $i => $dataSetDescription) {
$namedData[$dataSetDescription] = $dataSetValues[$i];
}
return $namedData;
}
/**
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
*
* @return array<array>
*/
private static function processDatasets(array $datasets): array
{
$processedDatasets = [];
foreach ($datasets as $index => $data) {
$processedDataset = [];
if (is_string($data)) {
$datasets[$index] = self::get($data);
}
if (is_callable($datasets[$index])) {
$datasets[$index] = call_user_func($datasets[$index]);
}
if ($datasets[$index] instanceof Traversable) {
$datasets[$index] = iterator_to_array($datasets[$index]);
}
foreach ($datasets[$index] as $key => $values) {
$values = is_array($values) ? $values : [$values];
$processedDataset[] = [
'label' => self::getDataSetDescription($key, $values),
'values' => $values,
];
}
$processedDatasets[] = $processedDataset;
}
return $processedDatasets;
}
/**
* @param array<array> $combinations
*
* @return array<array>
*/
private static function getDataSetsCombinations(array $combinations): array
{
$result = [[]];
foreach ($combinations as $index => $values) {
$tmp = [];
foreach ($result as $resultItem) {
foreach ($values as $value) {
$tmp[] = array_merge($resultItem, [$index => $value]);
}
}
$result = $tmp;
}
return $result;
}
/**
* @param int|string $key
* @param array<int, mixed> $data
*/
private static function getDataSetDescription($key, array $data): string
{
$exporter = new Exporter();
if (is_int($key)) {
return \sprintf('(%s)', $exporter->shortenedRecursiveExport($data));
}
return \sprintf('data set "%s"', $key);
}
}

View File

@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of after all already exist exception. * Creates a new Exception instance.
*/ */
public function __construct(string $filename) public function __construct(string $filename)
{ {
parent::__construct(sprintf('The afterAll already exist in the filename `%s`.', $filename)); parent::__construct(sprintf('The afterAll already exists in the filename `%s`.', $filename));
} }
} }

View File

@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class AfterEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class AfterEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of after each already exist exception. * Creates a new Exception instance.
*/ */
public function __construct(string $filename) public function __construct(string $filename)
{ {
parent::__construct(sprintf('The afterEach already exist in the filename `%s`.', $filename)); parent::__construct(sprintf('The afterEach already exists in the filename `%s`.', $filename));
} }
} }

View File

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

View File

@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class BeforeEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class BeforeEachAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of before each already exist exception. * Creates a new Exception instance.
*/ */
public function __construct(string $filename) public function __construct(string $filename)
{ {
parent::__construct(sprintf('The beforeEach already exist in the filename `%s`.', $filename)); parent::__construct(sprintf('The beforeEach already exists in the filename `%s`.', $filename));
} }
} }

View File

@ -12,13 +12,13 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
/** /**
* @internal * @internal
*/ */
final class DatasetAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of dataset already exist. * Creates a new Exception instance.
*/ */
public function __construct(string $name) public function __construct(string $name, string $scope)
{ {
parent::__construct(sprintf('A dataset with the name `%s` already exist.', $name)); parent::__construct(sprintf('A dataset with the name `%s` already exists in scope [%s].', $name, $scope));
} }
} }

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use Exception;
final class DatasetArgsCountMismatch extends Exception
{
public function __construct(string $dataName, int $requiredCount, int $suppliedCount)
{
parent::__construct(sprintf('Test expects %d arguments but dataset [%s] only provides %d', $requiredCount, $dataName, $suppliedCount));
}
}

View File

@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of dataset does not exist. * Creates a new Exception instance.
*/ */
public function __construct(string $name) public function __construct(string $name)
{ {

View File

@ -10,26 +10,22 @@ use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Exception\ExceptionInterface;
/** /**
* Creates a new instance of dataset is not present for test that has arguments.
*
* @internal * @internal
*/ */
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Create new exception instance. * Creates a new Exception instance.
* *
* @param array<string, string> $args A map of argument names to their typee * @param array<string, string> $arguments
*/ */
public function __construct(string $file, string $name, array $args) public function __construct(string $file, string $name, array $arguments)
{ {
parent::__construct(sprintf( parent::__construct(sprintf(
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", "A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
$name, $name,
count($args), count($arguments),
implode(', ', array_map(static function (string $arg, string $type): string { implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
return sprintf('%s $%s', $type, $arg);
}, array_keys($args), $args)),
$file, $file,
)); ));
} }

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use Exception;
/**
* @internal
*/
final class ExpectationNotFound extends Exception
{
/**
* Creates a new ExpectationNotFound instance from the given name.
*/
public static function fromName(string $name): ExpectationNotFound
{
return new self("Expectation [$name] does not exist.");
}
}

View File

@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of file not found. * Creates a new Exception instance.
*/ */
public function __construct(string $filename) public function __construct(string $filename)
{ {
parent::__construct(sprintf('The file or folder with the name `%s` not found.', $filename)); parent::__construct(sprintf('The file or folder with the name `%s` could not be found.', $filename));
} }
} }

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use LogicException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* @internal
*/
final class InvalidExpectation extends LogicException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* @param array<int, string> $methods
*
* @throws self
*/
public static function fromMethods(array $methods): never
{
throw new self(sprintf('Expectation [%s] is not valid.', implode('->', $methods)));
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
/**
* @internal
*/
final class InvalidExpectationValue extends InvalidArgumentException
{
/**
* @throws self
*/
public static function expected(string $type): never
{
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
}
}

View File

@ -12,10 +12,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
/** /**
* @internal * @internal
*/ */
final class InvalidConsoleArgument extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class InvalidOption extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of should not happen. * Creates a new Exception instance.
*/ */
public function __construct(string $message) public function __construct(string $message)
{ {

View File

@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class InvalidPestCommand extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class InvalidPestCommand extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of invalid pest command exception. * Creates a new Exception instance.
*/ */
public function __construct() public function __construct()
{ {
parent::__construct('Please run `./vendor/bin/pest` instead of `/vendor/bin/phpunit`.'); parent::__construct('Please run [./vendor/bin/pest] instead.');
} }
} }

View File

@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of missing dependency. * Creates a new Exception instance.
*/ */
public function __construct(string $feature, string $dependency) public function __construct(string $feature, string $dependency)
{ {

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Pest\Contracts\Panicable;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class NoDirtyTestsFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace, Panicable
{
/**
* Renders the panic on the given output.
*/
public function render(OutputInterface $output): void
{
$output->writeln([
'',
' <fg=white;options=bold;bg=blue> INFO </> No "dirty" tests found.',
'',
]);
}
/**
* The exit code to be used.
*/
public function exitCode(): int
{
return 0;
}
}

View File

@ -13,19 +13,18 @@ use RuntimeException;
final class ShouldNotHappen extends RuntimeException final class ShouldNotHappen extends RuntimeException
{ {
/** /**
* Creates a new instance of should not happen. * Creates a new Exception instance.
*/ */
public function __construct(Exception $exception) public function __construct(Exception $exception)
{ {
$message = $exception->getMessage(); $message = $exception->getMessage();
parent::__construct(sprintf(<<<EOF parent::__construct(sprintf(<<<'EOF'
This should not happen - please create an new issue here: https://github.com/pestphp/pest. This should not happen - please create an new issue here: https://github.com/pestphp/pest.
- Issue: %s Issue: %s
- PHP version: %s PHP version: %s
- Operating system: %s Operating system: %s
EOF EOF
, $message, phpversion(), PHP_OS), 1, $exception); , $message, phpversion(), PHP_OS), 1, $exception);
} }

View File

@ -15,10 +15,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class TestAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class TestAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of test already exist. * Creates a new Exception instance.
*/ */
public function __construct(string $fileName, string $description) public function __construct(string $fileName, string $description)
{ {
parent::__construct(sprintf('A test with the description `%s` already exist in the filename `%s`.', $description, $fileName)); parent::__construct(sprintf('A test with the description `%s` already exists in the filename `%s`.', $description, $fileName));
} }
} }

View File

@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class TestCaseAlreadyInUse extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class TestCaseAlreadyInUse extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of test case already in use. * Creates a new Exception instance.
*/ */
public function __construct(string $inUse, string $newOne, string $folder) public function __construct(string $inUse, string $newOne, string $folder)
{ {

View File

@ -15,7 +15,7 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{ {
/** /**
* Creates a new instance of after each already exist exception. * Creates a new Exception instance.
*/ */
public function __construct(string $testCaseClass) public function __construct(string $testCaseClass)
{ {

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* @internal
*/
final class TestDescriptionMissing extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new Exception instance.
*/
public function __construct(string $fileName)
{
parent::__construct(sprintf('Test description is missing in the filename `%s`.', $fileName));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,47 +2,50 @@
declare(strict_types=1); declare(strict_types=1);
namespace Pest; namespace Pest\Expectations;
use function expect;
use Pest\Expectation;
/** /**
* @internal * @internal
* *
* @mixin Expectation * @template TValue
*
* @mixin Expectation<TValue>
*/ */
final class Each final class EachExpectation
{ {
/** private bool $opposite = false;
* @var Expectation
*/
private $original;
/**
* @var bool
*/
private $opposite = false;
/** /**
* Creates an expectation on each item of the iterable "value". * Creates an expectation on each item of the iterable "value".
*
* @param Expectation<TValue> $original
*/ */
public function __construct(Expectation $original) public function __construct(private readonly Expectation $original)
{ {
$this->original = $original;
} }
/** /**
* Creates a new expectation. * Creates a new expectation.
* *
* @param mixed $value * @template TAndValue
*
* @param TAndValue $value
* @return Expectation<TAndValue>
*/ */
public function and($value): Expectation public function and(mixed $value): Expectation
{ {
return $this->original->and($value); return $this->original->and($value);
} }
/** /**
* Creates the opposite expectation for the value. * Creates the opposite expectation for the value.
*
* @return self<TValue>
*/ */
public function not(): Each public function not(): self
{ {
$this->opposite = true; $this->opposite = true;
@ -52,9 +55,10 @@ final class Each
/** /**
* Dynamically calls methods on the class with the given arguments on each item. * Dynamically calls methods on the class with the given arguments on each item.
* *
* @param array<int|string, mixed> $arguments * @param array<int|string, mixed> $arguments
* @return self<TValue>
*/ */
public function __call(string $name, array $arguments): Each public function __call(string $name, array $arguments): self
{ {
foreach ($this->original->value as $item) { foreach ($this->original->value as $item) {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
@ -68,8 +72,10 @@ final class Each
/** /**
* Dynamically calls methods on the class without any arguments on each item. * Dynamically calls methods on the class without any arguments on each item.
*
* @return self<TValue>
*/ */
public function __get(string $name): Each public function __get(string $name): self
{ {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
return $this->$name(); return $this->$name();

View File

@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace Pest\Expectations;
use Closure;
use Pest\Concerns\Retrievable;
use Pest\Expectation;
/**
* @internal
*
* @template TOriginalValue
* @template TValue
*
* @mixin Expectation<TOriginalValue>
*/
final class HigherOrderExpectation
{
use Retrievable;
/**
* @var Expectation<TValue>|EachExpectation<TValue>
*/
private Expectation|EachExpectation $expectation;
private bool $opposite = false;
private bool $shouldReset = false;
/**
* Creates a new higher order expectation.
*
* @param Expectation<TOriginalValue> $original
* @param TValue $value
*/
public function __construct(private readonly Expectation $original, mixed $value)
{
$this->expectation = $this->expect($value);
}
/**
* Creates the opposite expectation for the value.
*
* @return self<TOriginalValue, TValue>
*/
public function not(): self
{
$this->opposite = ! $this->opposite;
return $this;
}
/**
* Creates a new Expectation.
*
* @template TExpectValue
*
* @param TExpectValue $value
* @return Expectation<TExpectValue>
*/
public function expect(mixed $value): Expectation
{
return new Expectation($value);
}
/**
* Creates a new expectation.
*
* @template TExpectValue
*
* @param TExpectValue $value
* @return Expectation<TExpectValue>
*/
public function and(mixed $value): Expectation
{
return $this->expect($value);
}
/**
* Scope an expectation callback to the current value in
* the HigherOrderExpectation chain.
*
* @param Closure(Expectation<TValue>): void $expectation
* @return HigherOrderExpectation<TOriginalValue, TOriginalValue>
*/
public function scoped(Closure $expectation): self
{
$expectation->__invoke($this->expectation);
return new self($this->original, $this->original->value);
}
/**
* Creates a new expectation with the decoded JSON value.
*
* @return self<TOriginalValue, array<string|int, mixed>|bool>
*/
public function json(): self
{
return new self($this->original, $this->expectation->json()->value);
}
/**
* Dynamically calls methods on the class with the given arguments.
*
* @param array<int, mixed> $arguments
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
*/
public function __call(string $name, array $arguments): self
{
if (! $this->expectationHasMethod($name)) {
/* @phpstan-ignore-next-line */
return new self($this->original, $this->getValue()->$name(...$arguments));
}
return $this->performAssertion($name, $arguments);
}
/**
* Accesses properties in the value or in the expectation.
*
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
*/
public function __get(string $name): self
{
if ($name === 'not') {
return $this->not();
}
if (! $this->expectationHasMethod($name)) {
/** @var array<string, mixed>|object $value */
$value = $this->getValue();
return new self($this->original, $this->retrieve($name, $value));
}
return $this->performAssertion($name, []);
}
/**
* Determines if the original expectation has the given method name.
*/
private function expectationHasMethod(string $name): bool
{
if (method_exists($this->original, $name)) {
return true;
}
if ($this->original::hasMethod($name)) {
return true;
}
return $this->original::hasExtend($name);
}
/**
* Retrieve the applicable value based on the current reset condition.
*
* @return TOriginalValue|TValue
*/
private function getValue(): mixed
{
return $this->shouldReset ? $this->original->value : $this->expectation->value;
}
/**
* Performs the given assertion with the current expectation.
*
* @param array<int, mixed> $arguments
* @return self<TOriginalValue, TValue>
*/
private function performAssertion(string $name, array $arguments): self
{
/* @phpstan-ignore-next-line */
$this->expectation = ($this->opposite ? $this->expectation->not() : $this->expectation)->{$name}(...$arguments);
$this->opposite = false;
$this->shouldReset = true;
return $this;
}
}

View File

@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Pest\Expectations;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Arch\Expectations\ToBeUsedIn;
use Pest\Arch\Expectations\ToBeUsedInNothing;
use Pest\Arch\Expectations\ToUse;
use Pest\Arch\GroupArchExpectation;
use Pest\Arch\SingleArchExpectation;
use Pest\Exceptions\InvalidExpectation;
use Pest\Expectation;
use Pest\Support\Arr;
use Pest\Support\Exporter;
use PHPUnit\Framework\ExpectationFailedException;
/**
* @internal
*
* @template TValue
*
* @mixin Expectation<TValue>
*/
final class OppositeExpectation
{
/**
* Creates a new opposite expectation.
*
* @param Expectation<TValue> $original
*/
public function __construct(private readonly Expectation $original)
{
}
/**
* Asserts that the value array not has the provided $keys.
*
* @param array<int, int|string|array<int-string, mixed>> $keys
* @return Expectation<TValue>
*/
public function toHaveKeys(array $keys): Expectation
{
foreach ($keys as $k => $key) {
try {
if (is_array($key)) {
$this->toHaveKeys(array_keys(Arr::dot($key, $k.'.')));
} else {
$this->original->toHaveKey($key);
}
} catch (ExpectationFailedException) {
continue;
}
$this->throwExpectationFailedException('toHaveKey', [$key]);
}
return $this->original;
}
/**
* Asserts that the given expectation target does not use any of the given dependencies.
*
* @param array<int, string>|string $targets
*/
public function toUse(array|string $targets): ArchExpectation
{
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($this->original, $target)->opposite(
fn () => $this->throwExpectationFailedException('toUse', $target),
), is_string($targets) ? [$targets] : $targets));
}
/**
* @param array<int, string>|string $targets
*/
public function toOnlyUse(array|string $targets): never
{
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
}
public function toUseNothing(): never
{
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
}
/**
* Asserts that the given expectation dependency is not used.
*/
public function toBeUsed(): ArchExpectation
{
return ToBeUsedInNothing::make($this->original);
}
/**
* Asserts that the given expectation dependency is not used by any of the given targets.
*
* @param array<int, string>|string $targets
*/
public function toBeUsedIn(array|string $targets): ArchExpectation
{
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedIn::make($this->original, $target)->opposite(
fn () => $this->throwExpectationFailedException('toBeUsedIn', $target),
), is_string($targets) ? [$targets] : $targets));
}
public function toOnlyBeUsedIn(): never
{
throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedIn']);
}
/**
* Asserts that the given expectation dependency is not used.
*/
public function toBeUsedInNothing(): never
{
throw InvalidExpectation::fromMethods(['not', 'toBeUsedInNothing']);
}
/**
* Handle dynamic method calls into the original expectation.
*
* @param array<int, mixed> $arguments
* @return Expectation<TValue>|Expectation<mixed>|never
*/
public function __call(string $name, array $arguments): Expectation
{
try {
/* @phpstan-ignore-next-line */
$this->original->{$name}(...$arguments);
} catch (ExpectationFailedException) {
return $this->original;
}
$this->throwExpectationFailedException($name, $arguments);
}
/**
* Handle dynamic properties gets into the original expectation.
*
* @return Expectation<TValue>|Expectation<mixed>|never
*/
public function __get(string $name): Expectation
{
try {
$this->original->{$name}; // @phpstan-ignore-line
} catch (ExpectationFailedException) { // @phpstan-ignore-line
return $this->original;
}
$this->throwExpectationFailedException($name);
}
/**
* Creates a new expectation failed exception with a nice readable message.
*
* @param array<int, mixed>|string $arguments
*/
public function throwExpectationFailedException(string $name, array|string $arguments = []): never
{
$arguments = is_array($arguments) ? $arguments : [$arguments];
$exporter = Exporter::default();
$toString = fn ($argument): string => $exporter->shortenedExport($argument);
throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)), implode(' ', array_map(fn ($argument): string => $toString($argument), $arguments))));
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Annotations;
use Pest\Contracts\AddsAnnotations;
use Pest\Factories\Covers\CoversNothing as CoversNothingFactory;
use Pest\Factories\TestCaseMethodFactory;
/**
* @internal
*/
final class CoversNothing implements AddsAnnotations
{
/**
* {@inheritdoc}
*/
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
{
if (($method->covers[0] ?? null) instanceof CoversNothingFactory) {
$annotations[] = '@coversNothing';
}
return $annotations;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Annotations;
use Pest\Contracts\AddsAnnotations;
use Pest\Factories\TestCaseMethodFactory;
use Pest\Support\Str;
/**
* @internal
*/
final class Depends implements AddsAnnotations
{
/**
* {@inheritdoc}
*/
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
{
foreach ($method->depends as $depend) {
$depend = Str::evaluable($depend);
$annotations[] = "@depends $depend";
}
return $annotations;
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Annotations;
use Pest\Contracts\AddsAnnotations;
use Pest\Factories\TestCaseMethodFactory;
/**
* @internal
*/
final class Groups implements AddsAnnotations
{
/**
* {@inheritdoc}
*/
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
{
foreach ($method->groups as $group) {
$annotations[] = "@group $group";
}
return $annotations;
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Annotations;
use Pest\Contracts\AddsAnnotations;
use Pest\Factories\TestCaseMethodFactory;
final class TestDox implements AddsAnnotations
{
/**
* {@inheritdoc}
*/
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
{
$annotations[] = "@testdox $method->description";
return $annotations;
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Attributes;
use Pest\Factories\TestCaseMethodFactory;
/**
* @internal
*/
abstract class Attribute
{
/**
* Determine if the attribute should be placed above the class instead of above the method.
*/
public static bool $above = false;
/**
* @param array<int, string> $attributes
* @return array<int, string>
*/
public function __invoke(TestCaseMethodFactory $method, array $attributes): array // @phpstan-ignore-line
{
return $attributes;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Attributes;
use Pest\Factories\Covers\CoversClass;
use Pest\Factories\Covers\CoversFunction;
use Pest\Factories\TestCaseMethodFactory;
/**
* @internal
*/
final class Covers extends Attribute
{
/**
* Determine if the attribute should be placed above the classe instead of above the method.
*/
public static bool $above = true;
/**
* Adds attributes regarding the "covers" feature.
*
* @param array<int, string> $attributes
* @return array<int, string>
*/
public function __invoke(TestCaseMethodFactory $method, array $attributes): array
{
foreach ($method->covers as $covering) {
if ($covering instanceof CoversClass) {
// Prepend a backslash for FQN classes
if (str_contains($covering->class, '\\')) {
$covering->class = '\\'.$covering->class;
}
$attributes[] = "#[\PHPUnit\Framework\Attributes\CoversClass({$covering->class}::class)]";
} elseif ($covering instanceof CoversFunction) {
$attributes[] = "#[\PHPUnit\Framework\Attributes\CoversFunction('{$covering->function}')]";
}
}
return $attributes;
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Concerns;
use Pest\Support\HigherOrderMessageCollection;
trait HigherOrderable
{
/**
* The higher order messages that are chainable.
*/
public HigherOrderMessageCollection $chains;
/**
* The higher order messages that are "factory" proxyable.
*/
public HigherOrderMessageCollection $factoryProxies;
/**
* The higher order messages that are proxyable.
*/
public HigherOrderMessageCollection $proxies;
/**
* Boot the higher order properties.
*/
private function bootHigherOrderable(): void
{
$this->chains = new HigherOrderMessageCollection();
$this->factoryProxies = new HigherOrderMessageCollection();
$this->proxies = new HigherOrderMessageCollection();
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Covers;
/**
* @internal
*/
final class CoversClass
{
public function __construct(public string $class)
{
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Covers;
/**
* @internal
*/
final class CoversFunction
{
public function __construct(public string $function)
{
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Covers;
/**
* @internal
*/
final class CoversNothing
{
}

View File

@ -4,16 +4,18 @@ declare(strict_types=1);
namespace Pest\Factories; namespace Pest\Factories;
use Closure;
use ParseError; use ParseError;
use Pest\Concerns; use Pest\Concerns;
use Pest\Contracts\AddsAnnotations;
use Pest\Contracts\HasPrintableTestCaseName; use Pest\Contracts\HasPrintableTestCaseName;
use Pest\Datasets; use Pest\Exceptions\DatasetMissing;
use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\ShouldNotHappen;
use Pest\Support\HigherOrderMessageCollection; use Pest\Exceptions\TestAlreadyExist;
use Pest\Exceptions\TestDescriptionMissing;
use Pest\Factories\Concerns\HigherOrderable;
use Pest\Support\Reflection;
use Pest\Support\Str; use Pest\Support\Str;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use RuntimeException; use RuntimeException;
@ -22,219 +24,246 @@ use RuntimeException;
*/ */
final class TestCaseFactory final class TestCaseFactory
{ {
/** use HigherOrderable;
* Holds the test filename.
*
* @readonly
*
* @var string
*/
public $filename;
/** /**
* Marks this test case as only. * The list of annotations.
* *
* @readonly * @var array<int, class-string<AddsAnnotations>>
*
* @var bool
*/ */
public $only = false; private const ANNOTATIONS = [
Annotations\Depends::class,
Annotations\Groups::class,
Annotations\CoversNothing::class,
Annotations\TestDox::class,
];
/** /**
* Holds the test description. * The list of attributes.
* *
* If the description is null, means that it * @var array<int, class-string<\Pest\Factories\Attributes\Attribute>>
* will be created with the given assertions.
*
* @var string|null
*/ */
public $description; private const ATTRIBUTES = [
Attributes\Covers::class,
];
/** /**
* Holds the test closure. * The FQN of the Test Case class.
* *
* @readonly * @var class-string
*
* @var Closure
*/ */
public $test; public string $class = TestCase::class;
/** /**
* Holds the dataset, if any. * The list of class methods.
* *
* @var array<Closure|iterable<int|string, mixed>|string> * @var array<string, TestCaseMethodFactory>
*/ */
public $datasets = []; public array $methods = [];
/** /**
* The FQN of the test case class. * The list of class traits.
* *
* @var string * @var array <int, class-string>
*/ */
public $class = TestCase::class; public array $traits = [
/**
* An array of FQN of the class traits.
*
* @var array <int, string>
*/
public $traits = [
Concerns\Testable::class, Concerns\Testable::class,
Concerns\Expectable::class, Concerns\Expectable::class,
]; ];
/** /**
* Holds the higher order messages * Creates a new Factory instance.
* for the factory that are proxyble.
*
* @var HigherOrderMessageCollection
*/ */
public $factoryProxies; public function __construct(
public string $filename
/** ) {
* Holds the higher order $this->bootHigherOrderable();
* messages that are proxyble.
*
* @var HigherOrderMessageCollection
*/
public $proxies;
/**
* Holds the higher order
* messages that are chainable.
*
* @var HigherOrderMessageCollection
*/
public $chains;
/**
* Creates a new anonymous test case pending object.
*/
public function __construct(string $filename, string $description = null, Closure $closure = null)
{
$this->filename = $filename;
$this->description = $description;
$this->test = $closure ?? function (): void {
if (Assert::getCount() === 0) {
self::markTestIncomplete(); // @phpstan-ignore-line
}
};
$this->factoryProxies = new HigherOrderMessageCollection();
$this->proxies = new HigherOrderMessageCollection();
$this->chains = new HigherOrderMessageCollection();
} }
/** public function make(): void
* Builds the anonymous test case.
*
* @return array<int, TestCase>
*/
public function build(TestSuite $testSuite): array
{ {
if ($this->description === null) { $methods = $this->methods;
throw ShouldNotHappen::fromMessage('Description can not be empty.');
if ($methods !== []) {
$this->evaluate($this->filename, $methods);
} }
$chains = $this->chains;
$proxies = $this->proxies;
$factoryTest = $this->test;
/**
* @return mixed
*/
$test = function () use ($chains, $proxies, $factoryTest) {
$proxies->proxy($this);
$chains->chain($this);
/* @phpstan-ignore-next-line */
return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args());
};
$className = $this->makeClassFromFilename($this->filename);
$createTest = function ($description, $data) use ($className, $test) {
$testCase = new $className($test, $description, $data);
$this->factoryProxies->proxy($testCase);
return $testCase;
};
$datasets = Datasets::resolve($this->description, $this->datasets);
return array_map($createTest, array_keys($datasets), $datasets);
} }
/** /**
* Makes a fully qualified class name from the given filename. * Creates a Test Case class using a runtime evaluate.
*
* @param array<string, TestCaseMethodFactory> $methods
*/ */
public function makeClassFromFilename(string $filename): string public function evaluate(string $filename, array $methods): void
{ {
if ('\\' === DIRECTORY_SEPARATOR) { if ('\\' === DIRECTORY_SEPARATOR) {
// In case Windows, strtolower drive name, like in UsesCall. // In case Windows, strtolower drive name, like in UsesCall.
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', function ($match): string { $filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn ($match): string => strtolower($match['drive']), $filename);
return strtolower($match['drive']);
}, $filename);
} }
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename))); $filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
$rootPath = TestSuite::getInstance()->rootPath; $rootPath = TestSuite::getInstance()->rootPath;
$relativePath = str_replace($rootPath . DIRECTORY_SEPARATOR, '', $filename); $relativePath = str_replace($rootPath.DIRECTORY_SEPARATOR, '', $filename);
$relativePath = dirname(ucfirst($relativePath)) . DIRECTORY_SEPARATOR . basename($relativePath, '.php');
$relativePath = ltrim($relativePath, DIRECTORY_SEPARATOR);
$basename = basename($relativePath, '.php');
$dotPos = strpos($basename, '.');
if ($dotPos !== false) {
$basename = substr($basename, 0, $dotPos);
}
$relativePath = dirname(ucfirst($relativePath)).DIRECTORY_SEPARATOR.$basename;
$relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath); $relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
// Strip out any %-encoded octets. // Strip out any %-encoded octets.
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath); $relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
// Remove escaped quote sequences (maintain namespace) // Remove escaped quote sequences (maintain namespace)
$relativePath = str_replace(array_map(function (string $quote): string { $relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath);
return sprintf('\\%s', $quote);
}, ['\'', '"']), '', $relativePath);
// Limit to A-Z, a-z, 0-9, '_', '-'. // Limit to A-Z, a-z, 0-9, '_', '-'.
$relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath); $relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
$classFQN = 'P\\' . $relativePath; $classFQN = 'P\\'.$relativePath;
if (class_exists($classFQN)) { if (class_exists($classFQN)) {
return $classFQN; return;
} }
$hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class); $hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class);
$traitsCode = sprintf('use %s;', implode(', ', array_map(function ($trait): string { $traitsCode = sprintf('use %s;', implode(', ', array_map(
return sprintf('\%s', $trait); static fn ($trait): string => sprintf('\%s', $trait), $this->traits))
}, $this->traits))); );
$partsFQN = explode('\\', $classFQN); $partsFQN = explode('\\', $classFQN);
$className = array_pop($partsFQN); $className = array_pop($partsFQN);
$namespace = implode('\\', $partsFQN); $namespace = implode('\\', $partsFQN);
$baseClass = sprintf('\%s', $this->class); $baseClass = sprintf('\%s', $this->class);
if ('' === trim($className)) { if ('' === trim($className)) {
$className = 'InvalidTestName' . Str::random(); $className = 'InvalidTestName'.Str::random();
$classFQN .= $className;
} }
$classAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => $attribute::$above);
$methodAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => ! $attribute::$above);
$classAttributes = [];
foreach ($classAvailableAttributes as $attribute) {
$classAttributes = array_reduce(
$methods,
fn (array $carry, TestCaseMethodFactory $methodFactory): array => (new $attribute())->__invoke($methodFactory, $carry),
$classAttributes
);
}
$methodsCode = implode('', array_map(
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(
self::ANNOTATIONS,
$methodAvailableAttributes
),
$methods
));
$classAttributesCode = implode('', array_map(
static fn (string $attribute): string => sprintf("\n%s", $attribute),
array_unique($classAttributes),
));
try { try {
eval(" $classCode = <<<PHP
namespace $namespace; namespace $namespace;
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN { use Pest\Repositories\DatasetsRepository as __PestDatasets;
$traitsCode use Pest\TestSuite as __PestTestSuite;
private static \$__filename = '$filename'; /**
} * @testdox $filename
"); */
$classAttributesCode
#[\AllowDynamicProperties]
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
$traitsCode
private static \$__filename = '$filename';
$methodsCode
}
PHP;
eval($classCode); // @phpstan-ignore-line
} catch (ParseError $caught) { } catch (ParseError $caught) {
throw new RuntimeException(sprintf('Unable to create test case for test file at %s', $filename), 1, $caught); throw new RuntimeException(sprintf(
"Unable to create test case for test file at %s. \n %s",
$filename,
$classCode
), 1, $caught);
} }
return $classFQN;
} }
/** /**
* Determine if the test case will receive argument input from Pest, or not. * Adds the given Method to the Test Case.
*/ */
public function receivesArguments(): bool public function addMethod(TestCaseMethodFactory $method): void
{ {
return count($this->datasets) > 0 if ($method->description === null) {
|| $this->factoryProxies->count('addDependencies') > 0; throw new TestDescriptionMissing($method->filename);
}
if (array_key_exists($method->description, $this->methods)) {
throw new TestAlreadyExist($method->filename, $method->description);
}
if (! $method->receivesArguments()) {
if ($method->closure === null) {
throw ShouldNotHappen::fromMessage('The test closure may not be empty.');
}
$arguments = Reflection::getFunctionArguments($method->closure);
if ($arguments !== []) {
throw new DatasetMissing($method->filename, $method->description, $arguments);
}
}
$this->methods[$method->description] = $method;
}
/**
* Checks if a test case has a method.
*/
public function hasMethod(string $methodName): bool
{
foreach ($this->methods as $method) {
if ($method->description === null) {
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
}
if (Str::evaluable($method->description) === $methodName) {
return true;
}
}
return false;
}
/**
* Gets a Method by the given name.
*/
public function getMethod(string $methodName): TestCaseMethodFactory
{
foreach ($this->methods as $method) {
if ($method->description === null) {
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
}
if (Str::evaluable($method->description) === $methodName) {
return $method;
}
}
throw ShouldNotHappen::fromMessage(sprintf('Method %s not found.', $methodName));
} }
} }

View File

@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace Pest\Factories;
use Closure;
use Pest\Contracts\AddsAnnotations;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Factories\Concerns\HigherOrderable;
use Pest\Repositories\DatasetsRepository;
use Pest\Support\Str;
use Pest\TestSuite;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
/**
* @internal
*/
final class TestCaseMethodFactory
{
use HigherOrderable;
/**
* Determines if the Test Case Method is a "todo".
*/
public bool $todo = false;
/**
* The Test Case Dataset, if any.
*
* @var array<Closure|iterable<int|string, mixed>|string>
*/
public array $datasets = [];
/**
* The Test Case depends, if any.
*
* @var array<int, string>
*/
public array $depends = [];
/**
* The Test Case groups, if any.
*
* @var array<int, string>
*/
public array $groups = [];
/**
* The covered classes and functions, if any.
*
* @var array<int, \Pest\Factories\Covers\CoversClass|\Pest\Factories\Covers\CoversFunction|\Pest\Factories\Covers\CoversNothing>
*/
public array $covers = [];
/**
* Creates a new Factory instance.
*/
public function __construct(
public string $filename,
public ?string $description,
public ?Closure $closure,
) {
$this->closure ??= function (): void {
Assert::getCount() > 0 ?: self::markTestIncomplete(); // @phpstan-ignore-line
};
$this->bootHigherOrderable();
}
/**
* Makes the Test Case classes.
*/
public function getClosure(TestCase $concrete): Closure
{
$concrete::flush(); // @phpstan-ignore-line
if ($this->description === null) {
throw ShouldNotHappen::fromMessage('Description can not be empty.');
}
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename);
$testCase->factoryProxies->proxy($concrete);
$this->factoryProxies->proxy($concrete);
$method = $this;
return function () use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line
/* @var TestCase $this */
$testCase->proxies->proxy($this);
$method->proxies->proxy($this);
$testCase->chains->chain($this);
$method->chains->chain($this);
return \Pest\Support\Closure::bind($closure, $this, self::class)(...func_get_args());
};
}
/**
* Determine if the test case will receive argument input from Pest, or not.
*/
public function receivesArguments(): bool
{
return $this->datasets !== [] || $this->depends !== [];
}
/**
* Creates a PHPUnit method as a string ready for evaluation.
*
* @param array<int, class-string<AddsAnnotations>> $annotationsToUse
* @param array<int, class-string<\Pest\Factories\Attributes\Attribute>> $attributesToUse
*/
public function buildForEvaluation(array $annotationsToUse, array $attributesToUse): string
{
if ($this->description === null) {
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
}
$methodName = Str::evaluable($this->description);
$datasetsCode = '';
$annotations = ['@test'];
$attributes = [];
foreach ($annotationsToUse as $annotation) {
$annotations = (new $annotation())->__invoke($this, $annotations);
}
foreach ($attributesToUse as $attribute) {
$attributes = (new $attribute())->__invoke($this, $attributes);
}
if ($this->datasets !== []) {
$dataProviderName = $methodName.'_dataset';
$annotations[] = "@dataProvider $dataProviderName";
$datasetsCode = $this->buildDatasetForEvaluation($methodName, $dataProviderName);
}
$annotations = implode('', array_map(
static fn ($annotation): string => sprintf("\n * %s", $annotation), $annotations,
));
$attributes = implode('', array_map(
static fn ($attribute): string => sprintf("\n %s", $attribute), $attributes,
));
return <<<PHP
/**$annotations
*/
$attributes
public function $methodName()
{
\$test = \Pest\TestSuite::getInstance()->tests->get(self::\$__filename)->getMethod(\$this->name())->getClosure(\$this);
return \$this->__runTest(
\$test,
...func_get_args(),
);
}
$datasetsCode
PHP;
}
/**
* Creates a PHPUnit Data Provider as a string ready for evaluation.
*/
private function buildDatasetForEvaluation(string $methodName, string $dataProviderName): string
{
DatasetsRepository::with($this->filename, $methodName, $this->datasets);
return <<<EOF
public static function $dataProviderName()
{
return __PestDatasets::get(self::\$__filename, "$methodName");
}
EOF;
}
}

View File

@ -2,35 +2,34 @@
declare(strict_types=1); declare(strict_types=1);
use Pest\Datasets;
use Pest\Expectation; use Pest\Expectation;
use Pest\PendingObjects\AfterEachCall; use Pest\PendingCalls\AfterEachCall;
use Pest\PendingObjects\BeforeEachCall; use Pest\PendingCalls\BeforeEachCall;
use Pest\PendingObjects\TestCall; use Pest\PendingCalls\TestCall;
use Pest\PendingObjects\UsesCall; use Pest\PendingCalls\UsesCall;
use Pest\Repositories\DatasetsRepository;
use Pest\Support\Backtrace; use Pest\Support\Backtrace;
use Pest\Support\Extendable; use Pest\Support\DatasetInfo;
use Pest\Support\HigherOrderTapProxy; use Pest\Support\HigherOrderTapProxy;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** if (! function_exists('expect')) {
* Creates a new expectation. /**
* * Creates a new expectation.
* @param mixed $value the Value *
* * @template TValue
* @return Expectation|Extendable *
*/ * @param TValue|null $value
function expect($value = null) * @return Expectation<TValue|null>
{ */
if (func_num_args() === 0) { function expect(mixed $value = null): Expectation
return new Extendable(Expectation::class); {
return new Expectation($value);
} }
return new Expectation($value);
} }
if (!function_exists('beforeAll')) { if (! function_exists('beforeAll')) {
/** /**
* Runs the given closure before all tests in the current file. * Runs the given closure before all tests in the current file.
*/ */
@ -40,11 +39,11 @@ if (!function_exists('beforeAll')) {
} }
} }
if (!function_exists('beforeEach')) { if (! function_exists('beforeEach')) {
/** /**
* Runs the given closure before each test in the current file. * Runs the given closure before each test in the current file.
* *
* @return BeforeEachCall|TestCase|mixed * @return HigherOrderTapProxy<TestCall|TestCase>|TestCall|mixed
*/ */
function beforeEach(Closure $closure = null): BeforeEachCall function beforeEach(Closure $closure = null): BeforeEachCall
{ {
@ -54,32 +53,36 @@ if (!function_exists('beforeEach')) {
} }
} }
if (!function_exists('dataset')) { if (! function_exists('dataset')) {
/** /**
* Registers the given dataset. * Registers the given dataset.
* *
* @param Closure|iterable<int|string, mixed> $dataset * @param Closure|iterable<int|string, mixed> $dataset
*/ */
function dataset(string $name, $dataset): void function dataset(string $name, Closure|iterable $dataset): void
{ {
Datasets::set($name, $dataset); $scope = DatasetInfo::scope(Backtrace::datasetsFile());
DatasetsRepository::set($name, $dataset, $scope);
} }
} }
if (!function_exists('uses')) { if (! function_exists('uses')) {
/** /**
* The uses function binds the given * The uses function binds the given
* arguments to test closures. * arguments to test closures.
*
* @param class-string ...$classAndTraits
*/ */
function uses(string ...$classAndTraits): UsesCall function uses(string ...$classAndTraits): UsesCall
{ {
$filename = Backtrace::file(); $filename = Backtrace::file();
return new UsesCall($filename, $classAndTraits); return new UsesCall($filename, array_values($classAndTraits));
} }
} }
if (!function_exists('test')) { if (! function_exists('test')) {
/** /**
* Adds the given closure as a test. The first argument * Adds the given closure as a test. The first argument
* is the test description; the second argument is * is the test description; the second argument is
@ -87,7 +90,7 @@ if (!function_exists('test')) {
* *
* @return TestCall|TestCase|mixed * @return TestCall|TestCase|mixed
*/ */
function test(string $description = null, Closure $closure = null) function test(string $description = null, Closure $closure = null): HigherOrderTapProxy|TestCall
{ {
if ($description === null && TestSuite::getInstance()->test !== null) { if ($description === null && TestSuite::getInstance()->test !== null) {
return new HigherOrderTapProxy(TestSuite::getInstance()->test); return new HigherOrderTapProxy(TestSuite::getInstance()->test);
@ -99,7 +102,7 @@ if (!function_exists('test')) {
} }
} }
if (!function_exists('it')) { if (! function_exists('it')) {
/** /**
* Adds the given closure as a test. The first argument * Adds the given closure as a test. The first argument
* is the test description; the second argument is * is the test description; the second argument is
@ -111,15 +114,36 @@ if (!function_exists('it')) {
{ {
$description = sprintf('it %s', $description); $description = sprintf('it %s', $description);
return test($description, $closure); /** @var TestCall $test */
$test = test($description, $closure);
return $test;
} }
} }
if (!function_exists('afterEach')) { if (! function_exists('todo')) {
/**
* Adds the given todo test. Internally, this test
* is marked as incomplete. Yet, Collision, Pest's
* printer, will display it as a "todo" test.
*
* @return TestCall|TestCase|mixed
*/
function todo(string $description): TestCall
{
$test = test($description);
assert($test instanceof TestCall);
return $test->todo();
}
}
if (! function_exists('afterEach')) {
/** /**
* Runs the given closure after each test in the current file. * Runs the given closure after each test in the current file.
* *
* @return AfterEachCall|TestCase|mixed * @return HigherOrderTapProxy<TestCall|TestCase>|TestCall|mixed
*/ */
function afterEach(Closure $closure = null): AfterEachCall function afterEach(Closure $closure = null): AfterEachCall
{ {
@ -129,7 +153,7 @@ if (!function_exists('afterEach')) {
} }
} }
if (!function_exists('afterAll')) { if (! function_exists('afterAll')) {
/** /**
* Runs the given closure after all tests in the current file. * Runs the given closure after all tests in the current file.
*/ */

Some files were not shown because too many files have changed in this diff Show More