Compare commits

...

1276 Commits

Author SHA1 Message Date
nuno maduro 7b18287374 feat: evals configuration 2026-07-07 19:10:35 +01:00
nuno maduro 33e3bf2888 chore: moves to phpunit 13.2 2026-07-07 13:40:02 +01:00
nuno maduro 72ab506d7b Merge branch '4.x' into 5.x 2026-07-06 18:47:25 +01:00
nuno maduro 5dc49a71d6 release: v4.7.5 2026-07-06 18:06:29 +01:00
nuno maduro 1adb484d3f chore: bumps deps 2026-07-06 18:05:35 +01:00
nuno maduro aadf7e92a6 fix: escape generated test case filename (#1746)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:18:26 +01:00
nuno maduro df42b9ed7b chore: style 2026-07-06 13:17:11 +01:00
dependabot[bot] 632ae79c8b build(deps): bump actions/cache in the github-actions group (#1745)
Bumps the github-actions group with 1 update: [actions/cache](https://github.com/actions/cache).


Updates `actions/cache` from 5.0.5 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 12:57:08 +01:00
nuno maduro b5fed82883 chore: style 2026-06-25 20:24:28 +01:00
nuno maduro 145e20db75 chore: merges 4.x 2026-06-25 20:20:20 +01:00
nuno maduro ee2e97e932 release: v4.7.4 2026-06-25 20:09:05 +01:00
nuno maduro b561236325 chore: bumps phpunit 2026-06-25 20:09:05 +01:00
dependabot[bot] e685179b7e Bump actions/checkout from 6.0.3 to 7.0.0 in the github-actions group (#1737)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.3 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 20:03:39 +01:00
Conor Murphy ac8feafdcc fix: guard Tia argv scanning against non-string args (#1736)
The Tia plugin scans the raw argv with str_starts_with() in
argumentPresent() and hasExplicitPathArgument(). In the parallel worker
path (bin/worker.php) the unserialized argv can contain an integer
--random-order-seed value as a separate element, which made
str_starts_with() throw:

    TypeError: str_starts_with(): Argument #1 ($haystack) must be of
    type string, int given

This is the same class of bug as #1206, which was only fixed in
HandleArguments; it resurfaced in the newer Tia plugin. Cast each argv
element to string before scanning, mirroring the #1206 fix.

Adds a regression test, and updates the parallel count assertion and the
success snapshot to account for it.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:03:06 +01:00
Sonali dudhia b49cd150c9 feat: add toBeEmail expectation (#1735)
Adds `toBeEmail()` backed by `filter_var(FILTER_VALIDATE_EMAIL)`.
Mirrors the existing `toBeUrl()` pattern — no new dependencies, pure PHP.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:51:35 +01:00
Norby Baruani 5cfb4133bf [5.x] Make time-based sharding namespace-agnostic and forward --test-directory (#1677)
* refactor(shard): extract parseListTestsOutput for testability

* test(shard): characterize parseListTestsOutput current behavior

* fix(shard): parse any PHP FQCN namespace from --list-tests

* fix(shard): forward --test-directory to list-tests subprocess

* chore: lint + snapshot fixups

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(shard): make extracted helpers private, test via reflection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert visual_snapshot_of_help_command_output

* revert visual_snapshot_of_help_command_output

* keep function removeParallelArguments

* strip --processes argument when building list-tests command

The removeParallelArguments method was not filtering --processes flags, causing the list-tests subprocess to fail when parallel execution was enabled. This prevented time-based sharding from working correctly with the --parallel option.

Now both --parallel/-p and --processes arguments are removed from the command used to enumerate tests, ensuring the subprocess runs successfully.

* test: re-add namespace-agnostic sharding tests

- 5.x merge kept describe()-style test file, dropped PR #1677 tests for parseListTestsOutput + buildListTestsCommand. Re-add them in matching style.
- Also fix removeParallelArguments test broken by merge: source array_values() + strips --processes, so expects ['bin/pest','tests/'].

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-14 13:09:30 +01:00
nuno maduro 3c8bae5f05 chore: bumps dependencies 2026-06-12 20:30:22 +01:00
nuno maduro b2998bc69e chore: updates snapshots 2026-06-12 20:30:16 +01:00
nuno maduro 3876093cd2 fix: missing array values 2026-06-12 20:22:47 +01:00
Moshe Brodsky 932f8bcc07 consistent sharding logic when no shards file (#1710) 2026-06-12 20:19:30 +01:00
oddvalue d393799d2a Optimize buildFilterArgument in Shard plugin for compact regex generation and add comprehensive tests (#1675) 2026-06-12 20:06:50 +01:00
nuno maduro 0d7814ca16 chore: update snapshots 2026-06-12 18:02:06 +01:00
flap152 8467c64c22 fix: popArgument drops duplicate arguments breaking --parallel --exclude-gropup= (#1674)
* fix: popArgument drops duplicate arguments breaking --parallel multi-exclude-group

Fixes #1437

* fix: ensure popArgument handles duplicate arguments

* fix: update expected test results and snapshots after rebase

---------

Signed-off-by: nuno maduro <enunomaduro@gmail.com>
Co-authored-by: nuno maduro <enunomaduro@gmail.com>
2026-06-12 17:58:37 +01:00
oddvalue 97714a7088 Enforce filter length validation and add tests for Shard plugin (#1673) 2026-06-12 17:56:32 +01:00
nuno maduro afb582616d chore: points to 5.x 2026-06-12 07:29:53 +01:00
nuno maduro 15e9b6a507 chore: snapshots 2026-06-12 07:22:13 +01:00
nuno maduro 520ce29376 Merge branch '4.x' into 5.x 2026-06-12 07:22:01 +01:00
nuno maduro 87882a8561 fix: exit code 2026-06-12 06:57:27 +01:00
nuno maduro 77ef7e0df1 ci: bumps setup-php pinned commit 2026-06-12 06:51:58 +01:00
dependabot[bot] 12100dd901 Bump actions/checkout from 6.0.2 to 6.0.3 in the github-actions group (#1730)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 06:51:20 +01:00
nuno maduro 20c12d006e docs: fixes alt 2026-06-12 06:32:20 +01:00
nuno maduro 3aab02d2eb ci: fixes dd test 2026-06-11 10:50:24 +01:00
nuno maduro 553aac65e6 release: v4.7.3 2026-06-11 10:40:18 +01:00
Punyapal Shah be49a3ce18 Fix: dd (#1692)
* fix: update dd method to return never type

Co-authored-by: Copilot <copilot@github.com>

* fix: enhance var_dump calls to accept additional arguments

* fix: update dd method to handle paratest and collision printer environments

* test: add dd method tests for ExpectationFailedException in parallel mode

Co-authored-by: Copilot <copilot@github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
2026-06-11 10:16:07 +01:00
Sonali dudhia 774a340400 feat: add toBeUlid assertion and isUlid validation method (#1726) 2026-06-11 10:14:00 +01:00
Chetan 04c9d41895 fix: update gitlab urls for issues and prs to match new format (#1728)
* fix: update gitlab urls for issues and prs to match new format

Added an optional host parameter to the gitlab method, defaulting to 'gitlab.com'. Updated the issues and prs URLs to include '/-/' in the path, which is the new format for GitLab URLs. This change ensures that the URLs generated for GitLab projects are correct and reflect the new structure of GitLab's URLs for issues and merge requests.

Host parameter allows users to specify a custom GitLab instance if they are using a self-hosted version of GitLab, while still maintaining the default behavior for users who are using gitlab.com.

* fix: update gitlab method to use hostname parameter correctly
2026-06-11 10:13:26 +01:00
nuno maduro dfb7b870af fix: retry output expected string 2026-06-11 10:12:41 +01:00
nuno maduro 37821e1108 chore: fixes output snapshots on team city 2026-06-11 10:12:32 +01:00
nuno maduro cd711a25d5 chore: bumps phpunit 2026-06-11 10:08:36 +01:00
nuno maduro eee60c9e11 fix: duplicated team city output 2026-06-11 10:08:27 +01:00
jp c40c8dbc24 [4.x] Fix Checks (#1709)
* fix(composer): set root version for feature-branch CI

pest-plugin-browser ^4.3.1 requires pestphp/pest ^4.4.5 on the root package.
Without an explicit version, Composer infers dev-<branch> on PR branches,
which fails composer update before tests run.

* set root version of composer

* fix indent
2026-06-06 01:23:08 +01:00
dependabot[bot] 2d93c9c373 Bump shivammathur/setup-php in the github-actions group (#1707)
Bumps the github-actions group with 1 update: [shivammathur/setup-php](https://github.com/shivammathur/setup-php).


Updates `shivammathur/setup-php` from 2.37.0 to 2.37.1
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/accd6127cb78bee3e8082180cb391013d204ef9f...7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-version: 2.37.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 17:55:17 +01:00
dependabot[bot] 3d5bba93f8 Bump shivammathur/setup-php in the github-actions group (#1706)
Bumps the github-actions group with 1 update: [shivammathur/setup-php](https://github.com/shivammathur/setup-php).


Updates `shivammathur/setup-php` from 2.37.0 to 2.37.1
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/accd6127cb78bee3e8082180cb391013d204ef9f...7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-version: 2.37.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-04 17:55:13 +01:00
nuno maduro e5ab3af05b ci: update dependabot config (add cooldown, single update entry) 2026-06-04 17:54:45 +01:00
nuno maduro 79bc7a8257 Merge branch '4.x' into 5.x 2026-06-01 07:09:57 +01:00
nuno maduro 40b88b62ef release: v4.7.2 2026-06-01 07:08:59 +01:00
nuno maduro e3361bc321 fix: don't ob_start with pao 2026-06-01 07:08:50 +01:00
nuno maduro fc48c1bd1e Merge branch '4.x' into 5.x 2026-06-01 06:33:35 +01:00
nuno maduro 92e76eb5ab ci: runs ci only against stable 2026-06-01 06:32:29 +01:00
nuno maduro da726beffc chore: merges 4.x 2026-06-01 06:28:44 +01:00
nuno maduro 4ef12b9aac Merge branch '4.x' into 5.x 2026-06-01 06:25:56 +01:00
nuno maduro bd22f478b8 chore: fixes issues with contracts and symfony 8.1 2026-06-01 06:24:42 +01:00
nuno maduro eeaac34cf6 release: v4.7.1 2026-06-01 05:42:12 +01:00
nuno maduro b9b07d8983 chore: bump dependencies 2026-06-01 05:42:03 +01:00
nuno maduro 6aa7d2f891 fix: better fatal exceptions reporting 2026-06-01 05:41:58 +01:00
nuno maduro 4d550cecfd Merge branch '4.x' into 5.x 2026-05-13 12:20:46 +01:00
nuno maduro 1c21a7647a chore: fixes types 2026-05-13 12:20:00 +01:00
nuno maduro d649de1988 chore: add security policy 2026-05-12 02:48:25 +01:00
nuno maduro 783ca4bcd6 chore(deps): limit dependabot to maintained branches (4.x + 5.x) 2026-05-12 02:34:08 +01:00
nuno maduro ba07497219 chore: enable Dependabot version updates for GitHub Actions (#1700) 2026-05-11 22:12:07 -03:00
nuno maduro 34695843b3 chore: pin GitHub Actions to commit SHAs (#1699)
* chore: pin GitHub Actions to commit SHAs

* chore: pin GitHub Actions to commit SHAs
2026-05-11 22:12:04 -03:00
nuno maduro 1ca021dea6 chore: pin GitHub Actions to commit SHAs (#1695)
* chore: pin GitHub Actions to commit SHAs

* chore: pin GitHub Actions to commit SHAs
2026-05-12 02:08:47 +01:00
nuno maduro d17be9decd types 2026-05-04 08:02:09 -03:00
nuno maduro b828ddcec7 chore: style 2026-05-04 07:38:50 -03:00
nuno maduro f859bb179d Merge branch '4.x' into 5.x 2026-05-04 07:38:40 -03:00
nuno maduro 2fc75cfcf0 chore: updates snapshots 2026-05-03 13:09:32 -03:00
nuno maduro 6cc48f63f8 chore: style 2026-05-03 13:06:24 -03:00
nuno maduro e0419d1328 release: v4.7.0 2026-05-03 12:46:24 -03:00
nuno maduro faa6988801 Merge pull request #1682 from pestphp/feat/tia
[4.x] TIA Engine
2026-05-03 16:27:58 +01:00
nuno maduro c12247fafd Revert "wip"
This reverts commit 1b168aba1c.
2026-05-03 11:45:39 -03:00
nuno maduro 29b4452443 wip 2026-05-03 11:33:30 -03:00
nuno maduro 1b168aba1c wip 2026-05-03 11:30:13 -03:00
nuno maduro 6aabd977cd wip 2026-05-03 11:01:52 -03:00
nuno maduro a882543c53 wip 2026-05-03 10:57:28 -03:00
nuno maduro c250b9da4f wip 2026-05-03 10:37:17 -03:00
nuno maduro 46bc3dc628 wip 2026-05-03 10:34:44 -03:00
nuno maduro d3ce498b8a wip 2026-05-03 10:31:47 -03:00
nuno maduro e1a4b98b71 wip 2026-05-03 10:16:10 -03:00
nuno maduro 9afbcd5c18 wuip 2026-05-03 09:54:02 -03:00
nuno maduro 75593b6454 wip 2026-05-03 13:37:27 +01:00
nuno maduro 89590d6120 wip 2026-05-03 13:35:01 +01:00
nuno maduro fb0978c9bf wip 2026-05-03 13:26:48 +01:00
nuno maduro a3796daa42 wip 2026-05-03 13:24:10 +01:00
nuno maduro e3004db666 wip 2026-05-03 12:43:38 +01:00
nuno maduro 99cc4e0146 wip 2026-05-02 19:33:09 +01:00
nuno maduro a47e6f8fef wip 2026-05-02 19:30:14 +01:00
nuno maduro 536d79f765 wip 2026-05-02 19:20:55 +01:00
nuno maduro 65c0fbc528 wip 2026-05-02 19:07:41 +01:00
nuno maduro 9e4cf4b665 wip 2026-05-02 18:58:42 +01:00
nuno maduro 7bea819978 wip 2026-05-02 18:47:26 +01:00
nuno maduro 4280233b40 wip 2026-05-02 18:37:24 +01:00
nuno maduro d6db3a8a20 wip 2026-05-02 18:32:05 +01:00
nuno maduro 51c8ce4df6 wip 2026-05-02 18:31:32 +01:00
nuno maduro 5b8393b925 wip 2026-05-02 18:25:41 +01:00
nuno maduro e4d9b61fdf wip 2026-05-02 18:25:27 +01:00
nuno maduro e2d940cd53 wip 2026-05-02 18:25:21 +01:00
nuno maduro 380ccd30b4 wip 2026-05-02 18:03:25 +01:00
nuno maduro 31c200716d wip 2026-05-02 18:03:14 +01:00
nuno maduro 6add4da543 wip 2026-05-02 18:02:20 +01:00
nuno maduro 8ddcd3e853 wip 2026-05-02 18:02:13 +01:00
nuno maduro e3e178fd94 wip 2026-05-02 17:59:21 +01:00
nuno maduro 7b1ec9f003 wip 2026-05-02 17:59:13 +01:00
nuno maduro 1e48c5d473 wip 2026-05-02 17:59:00 +01:00
nuno maduro d00ec95dd9 wip 2026-05-02 17:58:55 +01:00
nuno maduro 89f3d6cb39 wip 2026-05-02 17:45:54 +01:00
nuno maduro a07a2e512a wip 2026-05-02 17:39:15 +01:00
nuno maduro 57eecb2b3d wip 2026-05-02 17:38:12 +01:00
nuno maduro 9f804dc954 wip 2026-05-02 17:38:08 +01:00
nuno maduro 7cbad4c589 wip 2026-05-02 17:38:01 +01:00
nuno maduro 5cae93b059 wip 2026-05-02 17:37:56 +01:00
nuno maduro df829ad19d wip 2026-05-02 17:37:47 +01:00
nuno maduro 635460653c wip 2026-05-02 17:37:34 +01:00
nuno maduro 1aa80dc398 wip 2026-05-02 17:18:35 +01:00
nuno maduro 8a14056111 wip 2026-05-02 17:15:46 +01:00
nuno maduro f247dd8e7b wip 2026-05-02 17:11:49 +01:00
nuno maduro 1c7c9754fd wip 2026-05-02 17:07:08 +01:00
nuno maduro 5f37939fda wip 2026-05-02 17:02:11 +01:00
nuno maduro 28305fcb7a wip 2026-05-02 16:35:52 +01:00
nuno maduro 5242803694 wip 2026-05-02 15:54:00 +01:00
nuno maduro 925935a7e8 wip 2026-05-02 15:33:38 +01:00
nuno maduro 460401c379 wip 2026-05-02 15:26:58 +01:00
nuno maduro 348b439172 wip 2026-05-02 15:15:53 +01:00
nuno maduro a4e77766c5 wip 2026-05-02 15:07:51 +01:00
nuno maduro 4a8c2d7d78 wip 2026-05-02 15:03:44 +01:00
nuno maduro 7d51601120 wip 2026-05-02 14:15:37 +01:00
nuno maduro 631bbe318b wip 2026-05-02 13:43:32 +01:00
nuno maduro 9b7c15d5b6 wip 2026-05-02 12:03:35 +01:00
nuno maduro 872796bd9b wip 2026-05-02 12:00:47 +01:00
nuno maduro c38d32ae86 wip 2026-05-02 09:49:33 +01:00
nuno maduro 6407c4f78f wip 2026-05-02 01:58:39 +01:00
nuno maduro 6e1bf63f6a wip 2026-05-02 01:40:35 +01:00
nuno maduro 1d3e8bb5dd wip 2026-05-02 01:03:06 +01:00
nuno maduro 3cc9b169e3 wip 2026-05-02 00:52:57 +01:00
nuno maduro c4911d046b wip 2026-05-02 00:06:04 +01:00
nuno maduro d0295f6168 wip 2026-05-01 23:59:25 +01:00
nuno maduro 21efbc3107 wip 2026-05-01 22:55:38 +01:00
nuno maduro e59b99cd73 wip 2026-05-01 22:51:55 +01:00
nuno maduro bf48e20880 wip 2026-05-01 22:36:15 +01:00
nuno maduro 53db68e005 wip 2026-05-01 22:31:00 +01:00
nuno maduro 34f1e9a7f2 fix 2026-05-01 21:51:09 +01:00
nuno maduro 57fd5ce042 wip 2026-05-01 21:50:56 +01:00
nuno maduro 3bcabfb63b fix 2026-05-01 21:50:52 +01:00
nuno maduro aa3a7c303a wip 2026-05-01 21:32:59 +01:00
nuno maduro 5c08a135f7 wip 2026-05-01 21:30:44 +01:00
nuno maduro 6e0e030d71 wip 2026-05-01 21:22:33 +01:00
nuno maduro b2c07561e7 wip 2026-05-01 20:54:24 +01:00
nuno maduro 97600b6f0b wip 2026-05-01 20:53:40 +01:00
nuno maduro 8a51f15d65 wip 2026-05-01 20:45:51 +01:00
nuno maduro a349f53964 wip 2026-05-01 20:42:14 +01:00
nuno maduro a725e774c0 wip 2026-05-01 20:28:39 +01:00
nuno maduro bed5e5b54a wip 2026-05-01 20:02:46 +01:00
nuno maduro 45b1d4ce20 wip 2026-05-01 19:50:54 +01:00
nuno maduro d106b70766 wip 2026-05-01 17:24:22 +01:00
nuno maduro 6ac6c1518e wip 2026-05-01 17:17:33 +01:00
nuno maduro fda515a17f wip 2026-05-01 16:42:01 +01:00
nuno maduro 0a97d3a288 asd 2026-05-01 15:33:06 +01:00
nuno maduro 3802fa80e6 asd 2026-05-01 15:19:19 +01:00
nuno maduro 5c3cbc14d2 wip 2026-05-01 15:07:10 +01:00
nuno maduro 6b9c768172 wip 2026-05-01 14:39:23 +01:00
nuno maduro 4a2fc179ae asd 2026-05-01 13:54:25 +01:00
nuno maduro b5bb2139dc wqdqwd 2026-05-01 12:57:12 +01:00
nuno maduro 07416a3c61 wip 2026-05-01 03:30:28 +01:00
nuno maduro 30b94e3034 qdw 2026-05-01 02:10:08 +01:00
nuno maduro be34eecb2f wip 2026-05-01 01:55:18 +01:00
nuno maduro 5d9f95f8d4 qwdqwd 2026-05-01 01:44:08 +01:00
nuno maduro 48b70a03d5 wip 2026-05-01 01:32:48 +01:00
nuno maduro 4b8642b972 wip 2026-05-01 00:48:31 +01:00
nuno maduro 8711d51eac fix 2026-05-01 00:19:44 +01:00
nuno maduro 58dfb6da64 wip 2026-04-30 22:12:53 +01:00
nuno maduro d7735d1faa wip 2026-04-30 22:00:56 +01:00
nuno maduro 6b59166f3c wip 2026-04-30 21:08:00 +01:00
nuno maduro 3a26028d17 wip 2026-04-30 20:58:06 +01:00
nuno maduro 3c91bf4ad2 wip 2026-04-30 20:51:57 +01:00
nuno maduro 6a434be0f6 wip 2026-04-30 20:45:36 +01:00
nuno maduro f355b99bbf wip 2026-04-29 22:59:56 +01:00
nuno maduro 95a00341e9 wip for now 2026-04-29 02:22:37 +01:00
nuno maduro 466259646d wip 2026-04-28 22:12:42 +01:00
nuno maduro 00f8d56083 wip 2026-04-28 21:41:20 +01:00
nuno maduro ca2dca592d wup 2026-04-28 21:34:40 +01:00
nuno maduro 405d8d4406 wip 2026-04-28 21:28:46 +01:00
nuno maduro b944ee5841 wip 2026-04-27 19:15:42 +01:00
nuno maduro f4e22dcafe wip 2026-04-27 18:57:41 +01:00
nuno maduro 339c1e8cac wip 2026-04-27 18:14:10 +01:00
nuno maduro d4c7362132 wip 2026-04-27 16:56:27 +01:00
nuno maduro 81bfdbf8fe wip 2026-04-27 13:16:05 +01:00
nuno maduro f45cbf43c5 wip 2026-04-27 13:11:48 +01:00
nuno maduro b9088d23fb wip 2026-04-27 13:03:07 +01:00
nuno maduro 7250185423 wip 2026-04-27 12:22:05 +01:00
nuno maduro e457eb0e9c wip 2026-04-27 11:15:59 +01:00
nuno maduro 48357c6f30 wip 2026-04-27 10:30:08 +01:00
nuno maduro b46f051550 wip 2026-04-23 17:32:27 -07:00
nuno maduro 3d3c5d41ac wip 2026-04-23 12:29:24 -07:00
nuno maduro caabebf2a1 wip 2026-04-23 10:56:17 -07:00
nuno maduro 470a5833d4 wip 2026-04-23 10:30:44 -07:00
nuno maduro c1feefbb9e wip 2026-04-23 09:44:12 -07:00
nuno maduro e876dba8ba wip 2026-04-23 09:29:56 -07:00
nuno maduro d9c18f9c02 wip 2026-04-22 09:03:10 -07:00
nuno maduro 660b57b365 wip 2026-04-22 08:42:32 -07:00
nuno maduro 68527c996f wip 2026-04-22 08:25:38 -07:00
nuno maduro c6a42a2b28 wip 2026-04-22 08:07:52 -07:00
nuno maduro fcf5c27914 chore: adds YouTube channel badge
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:56:04 -07:00
nuno maduro 856a370032 style 2026-04-21 09:44:26 -07:00
nuno maduro e24882c486 wip 2026-04-21 09:41:19 -07:00
nuno maduro 51fc380789 wip 2026-04-21 09:40:01 -07:00
nuno maduro f6609f4039 wip 2026-04-21 08:36:41 -07:00
nuno maduro 2941f9821f wip 2026-04-21 08:15:24 -07:00
nuno maduro ed399af43e wip 2026-04-21 07:41:50 -07:00
nuno maduro 0d66dc4322 chore: removes https 2026-04-21 07:26:19 -07:00
nuno maduro 7e4280bf83 chore: improves feedback 2026-04-21 07:13:08 -07:00
nuno maduro a5915b16ab wip 2026-04-20 20:58:38 -07:00
nuno maduro 1476b529a1 wip 2026-04-20 19:56:23 -07:00
nuno maduro 2892341c28 wip 2026-04-20 14:28:18 -07:00
nuno maduro 59e781e77b wip 2026-04-20 13:48:05 -07:00
nuno maduro 55a3394f8c wip 2026-04-20 13:31:43 -07:00
nuno maduro 0d99c33c4e wip 2026-04-20 13:16:59 -07:00
nuno maduro adc5aae6f8 wip 2026-04-20 13:00:41 -07:00
nuno maduro 980667e845 wip 2026-04-20 12:25:39 -07:00
nuno maduro 8c849c5f40 fix: missing warning 2026-04-20 12:18:32 -07:00
nuno maduro 47f1fc2d94 feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro 9c8033d60c feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro 42d1092a9e feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro c7e32f5d33 feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro d379128cc4 feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro f09d6f2064 feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro 494cc6e2a4 feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro f52a455773 fix 2026-04-20 10:09:07 -07:00
nuno maduro 184f5d2742 feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro 1d81069a2a feat(tia): continues to work on poc 2026-04-20 10:09:07 -07:00
nuno maduro 4b9bb77b54 feat(tia): adds poc 2026-04-20 10:09:07 -07:00
nuno maduro c440031e28 feat(tia): adds poc 2026-04-20 10:09:07 -07:00
nuno maduro 18bbca748f Merge branch '4.x' into 5.x 2026-04-18 07:03:46 -07:00
nuno maduro bff44562a9 release: v4.6.3 2026-04-18 06:51:25 -07:00
nuno maduro 9ebb990f96 chore: bumps phpunit 2026-04-18 06:51:17 -07:00
nuno maduro f142aad8ad Merge branch '4.x' into 5.x 2026-04-17 19:35:53 -07:00
nuno maduro cabff738f7 release: v4.6.2 2026-04-17 19:32:23 -07:00
nuno maduro 0746173a32 chore: bumps phpunit 2026-04-17 19:32:18 -07:00
nuno maduro 74a28d4f5e fix: wrapper runner 2026-04-17 07:29:03 -07:00
nuno maduro 6053e15d00 Merge branch '4.x' into 5.x 2026-04-17 06:07:14 -07:00
nuno maduro 87db0b4847 release: v4.6.1 2026-04-15 09:03:09 -07:00
nuno maduro 6ba373a772 chore: bumps phpunit 2026-04-15 08:49:34 -07:00
nuno maduro 945d476409 fix: allow to update individual screenshots 2026-04-15 08:34:06 -07:00
nuno maduro a8cf0fe2cb chore: improves CI 2026-04-15 08:20:50 -07:00
nuno maduro 2ae072bb95 feat: makes boot time much faster 2026-04-15 07:47:38 -07:00
nuno maduro 59d066950c chore: missing header 2026-04-15 07:47:22 -07:00
nuno maduro 0dd1aa72ef fix: updating snapshots in --parallel 2026-04-15 07:22:10 -07:00
nuno maduro 4e03cd3edb release: v4.6.0 2026-04-14 10:23:26 -07:00
nuno maduro eeab24e2bb Merge pull request #1671 from pestphp/feat/time-based-sharding
[4.x] Time based sharding
2026-04-14 18:18:09 +01:00
nuno maduro 9b64d5425a removes time balanced 2026-04-14 10:12:57 -07:00
nuno maduro 0acab1cbb4 wip 2026-04-14 09:53:57 -07:00
nuno maduro e616eab9fb wip 2026-04-14 09:36:38 -07:00
nuno maduro 7cbb1fcdb2 wip 2026-04-14 09:29:41 -07:00
nuno maduro cb5f6e1bd2 chore: style 2026-04-14 09:17:18 -07:00
nuno maduro 985dadd934 update 2026-04-14 09:16:32 -07:00
nuno maduro 10aee6045c feat(time-based-sharding): updates exception name 2026-04-14 09:08:52 -07:00
nuno maduro 4ac14b2528 feat(time-based-sharding): updates plugin 2026-04-14 08:34:41 -07:00
nuno maduro 2d649d765f chore: adjusts tests 2026-04-11 01:54:13 +01:00
nuno maduro 4fb4908570 Merge branch '4.x' into 5.x 2026-04-10 22:37:24 +01:00
nuno maduro 13c322bab3 ci: fixes incorrectCasing test 2026-04-10 20:51:40 +01:00
nuno maduro 3855249ce9 feat: adds --flaky cli option 2026-04-10 20:03:50 +01:00
nuno maduro f528bd8427 feat: adds flaky 2026-04-10 19:52:31 +01:00
nuno maduro acd8aafa63 fix: printer with --colors 2026-04-10 19:21:49 +01:00
nuno maduro e8d630e774 fix: printer with --colors 2026-04-10 19:21:41 +01:00
nuno maduro b6385dc865 fix: namespaces on toBeCasedCorrectly 2026-04-10 19:21:31 +01:00
nuno maduro 02dc8d7bcc chore: bumps deps 2026-04-10 19:21:18 +01:00
nuno maduro 729f18a152 fix: stacktrace with nested with calls 2026-04-10 17:25:05 +01:00
nuno maduro bdf60cea91 Merge pull request #1565 from louisbels/fix-dataset-method-chaining
fix: dataset inheritance with method chaining (beforeEach()->with(), describe()->with())
2026-04-10 17:05:25 +01:00
nuno maduro 3a8ee8291c Merge pull request #1628 from DevDavido/patch-1
fix: Parameter closure this type annotations in Functions
2026-04-10 16:58:39 +01:00
nuno maduro 654cb726c9 Merge branch '4.x' into patch-1 2026-04-10 16:58:26 +01:00
nuno maduro bce26aeaad Merge pull request #1634 from dbpolito/dataset_named_params
Dataset Named Parameters
2026-04-10 16:54:57 +01:00
nuno maduro 5948bcd71e chore: type improvements 2026-04-10 16:50:10 +01:00
nuno maduro 89006d83a9 chore: env 2026-04-10 16:27:44 +01:00
nuno maduro a8e974d64a chore: updates snapshots 2026-04-10 14:42:34 +01:00
nuno maduro 617b074049 Merge pull request #1626 from SimonBroekaert/feat/add_only-covered_option
feat: add '--only-covered' option to '--coverage'
2026-04-10 12:53:17 +01:00
nuno maduro 2eea71a664 Merge pull request #1624 from Vmadmax/fix/unicode-filename-filter
fix: preserve Unicode characters in filenames for --filter matching
2026-04-10 12:29:40 +01:00
nuno maduro 4b5374d507 Merge branch '4.x' into fix/unicode-filename-filter 2026-04-10 12:29:30 +01:00
nuno maduro 9085561ece chore: runs at 9am 2026-04-10 12:24:30 +01:00
nuno maduro b71bfc513a chore: guards 2026-04-10 12:23:49 +01:00
nuno maduro 75938ac9eb ci: updates deps 2026-04-10 12:18:28 +01:00
nuno maduro e766825f5b chore: fixes test:unit 2026-04-10 12:15:00 +01:00
nuno maduro 8a83a1a1a9 Merge pull request #1655 from stsepelin/fix/parallel-empty-suite-reporting
fix: nested dataset discovery and parallel invalid-dataset reporting
2026-04-10 11:59:48 +01:00
nuno maduro e63a886f98 Merge pull request #1661 from Avnsh1111/fix/opposite-expectation-truncated-message
fix: preserve full error message in not() expectation failures
2026-04-10 11:48:24 +01:00
nuno maduro 109bb22c5e Merge pull request #1615 from smirok/parallel-teamcity-concurrency-fix
fix: enhance support for --parallel and --teamcity arguments by restoring --teamcity for ParaTest and fixing teamcity output concurrency
2026-04-10 11:42:45 +01:00
nuno maduro 89dd212d84 Merge pull request #1580 from bibrokhim/patch-1
Add Rules to Laravel preset
2026-04-10 11:42:13 +01:00
nuno maduro cd07c6d966 Merge pull request #1569 from treyssatvincent/patch-1
add missing classes before toExtend on laravel preset
2026-04-10 11:41:53 +01:00
nuno maduro 8dddb47ad5 Merge branch '4.x' into fix-dataset-method-chaining 2026-04-10 11:41:13 +01:00
nuno maduro 3a6c2fab37 Merge pull request #1515 from yondifon/fix-trait-inheritance-detection
BugFix: Fix toUseTrait to detect inherited and nested traits
2026-04-10 11:39:56 +01:00
nuno maduro 281dbf6cf4 Merge pull request #1455 from SimonBroekaert/feat/to_be_cased_correctly_arch_test_assertion
feat: add toBeCasedCorrectly arch test assertion
2026-04-10 11:38:46 +01:00
nuno maduro 40c8429058 Merge pull request #1653 from orphanedrecord/fix/pest-php-stub-typo
Fix typo in Pest.php stubs
2026-04-10 11:35:41 +01:00
nuno maduro 8dd650fd05 Merge branch '4.x' into 5.x 2026-04-09 21:39:15 +01:00
nuno maduro d9d46c73f8 chore: stores statically the result 2026-04-09 21:36:49 +01:00
nuno maduro fbca346d7c fix: types 2026-04-07 14:40:55 +01:00
nuno maduro 3f13bca0f7 just in case 2026-04-07 14:37:13 +01:00
nuno maduro d3acb1c56a fix: coverage 2026-04-07 14:33:41 +01:00
avinash e601e6df31 fix: preserve full error message in not() expectation failures
When using not() expectations with custom error messages, the message
was truncated because throwExpectationFailedException() passed all
arguments through shortenedExport() which limits strings to ~40 chars.

Uses the full export() method for arguments instead of shortenedExport()
so custom error messages are displayed in their entirety.

Fixes #1533
2026-04-07 18:12:54 +05:30
nuno maduro 6fdbca1226 fix: parallel testing 2026-04-06 23:37:49 +01:00
nuno maduro 54359b895f Merge branch '4.x' into 5.x 2026-04-06 21:57:41 +01:00
nuno maduro e44c554a0b chore: bumps dependencies 2026-04-06 21:57:05 +01:00
nuno maduro 44c04bfce1 chore: bumps paratest 2026-04-06 14:41:38 +01:00
nuno maduro 271c680d3c Merge branch '4.x' into 5.x 2026-04-06 11:24:05 +01:00
nuno maduro 9797a71dbc feat(ai): allow temporary namesapce 2026-04-03 14:43:28 +01:00
nuno maduro c1a54df233 feat: --ai work in progress 2026-04-03 14:04:20 +01:00
nuno maduro 4a1d8d27b8 chore: bumps dependencies 2026-04-03 12:12:27 +01:00
nuno maduro 0f6924984c Merge branch '4.x' into 5.x 2026-04-03 12:02:36 +01:00
nuno maduro ce05ee9aad release: v4.4.4 2026-04-03 12:00:04 +01:00
nuno maduro 3d2ebdb273 bump: dependencies 2026-04-03 11:59:54 +01:00
nuno maduro 668ca9f5de feat: adds pao 2026-04-02 15:45:13 +01:00
nuno maduro f47b74445b Merge pull request #1657 from morpheus7CS/pint-formatting-rules-applied-to-stubs 2026-04-02 13:04:00 +01:00
Gal Jakic (We Wow Web) 6c42e7f4ea Laravel Pint default formatting applied to Pest-php.stub 2026-04-01 16:48:14 +02:00
dbpolito be3ff37517 Merge branch '4.x' of https://github.com/pestphp/pest into dataset_named_params 2026-03-26 18:08:26 -03:00
nuno maduro a087555383 bump: dependencies 2026-03-26 14:30:03 +00:00
Aleksandr Štšepelin 4b50cb486d Restore success snapshot coverage with lower memory limit 2026-03-25 23:59:29 +02:00
Aleksandr Štšepelin f7175ecfd7 Fix parallel dataset reporting and nested fixtures 2026-03-25 23:59:29 +02:00
Aleksandr Štšepelin 07737bc0b2 Fix parallel file selection and empty-suite reporting 2026-03-25 23:59:28 +02:00
nuno maduro f659a45311 Merge branch '4.x' into 5.x 2026-03-21 13:20:25 +00:00
nuno maduro e6ab897594 release: v4.4.3 2026-03-21 13:14:39 +00:00
nuno maduro a753b41409 chore: bumps phpunit 2026-03-21 13:14:35 +00:00
brianseymour 1a4c06bd6e Fix Pest comment typo while still honoring the Otwellian Waterfall 2026-03-18 20:51:45 -07:00
nuno maduro 12c1da29ee Merge branch '4.x' into 5.x 2026-03-10 21:21:24 +00:00
nuno maduro 5d42e8fe3a release: v4.4.2 2026-03-10 21:09:12 +00:00
nuno maduro 9d17b872dd chore: update stubs 2026-03-10 21:09:02 +00:00
nuno maduro 2a80101f42 chore: update styling 2026-03-10 21:06:28 +00:00
Nuno Maduro f7015fe59c chore: adjusts sponsors 2026-02-24 10:44:48 +00:00
nuno maduro 7281e0ded7 Add SerpApi to the list of sponsors
Add SerpApi as a sponsor in the README.
2026-02-20 01:18:43 +00:00
SimonBroekaert 1675dd1d41 chore: add tests for toBeCasedCorrectly() arch test 2026-02-17 19:03:46 +01:00
Nuno Maduro fa27c8daef chore: version 2026-02-17 17:52:40 +00:00
Nuno Maduro f0a08f0503 chore: missing types 2026-02-17 17:52:00 +00:00
Nuno Maduro 2c040c5b1f chore: style 2026-02-17 17:45:50 +00:00
Nuno Maduro a9ce1fd739 chore: code refactor 2026-02-17 17:45:34 +00:00
Nuno Maduro 3533356262 chore: updates snapshots 2026-02-17 17:44:56 +00:00
Nuno Maduro 4aa41d0b14 chore: bumps dependencies 2026-02-17 17:41:38 +00:00
Nuno Maduro e4ed60085c chore: bumps dependencies 2026-02-17 17:18:45 +00:00
Nuno Maduro e2b119655d chore: point pestphp dependencies to ^5.0.0 2026-02-17 17:13:36 +00:00
Nuno Maduro fcf5baf0a9 chore: start preparing for pest 5.x 2026-02-17 16:55:03 +00:00
SimonBroekaert df7b6c8454 feat: add toBeCasedCorrectly arch test assertion 2026-02-17 17:31:02 +01:00
Nuno Maduro 5de8693e3b chore: style 2026-02-17 16:15:58 +00:00
Nuno Maduro 7d80f1d20e chore: removes non used files 2026-02-17 15:34:32 +00:00
nuno maduro b3119cc120 Merge pull request #1562 from michaelw85/patch-1
[Fix] Pass test dir to worker
2026-02-17 15:33:16 +00:00
nuno maduro 4e294edf76 Merge pull request #1639 from imliam/patch-1
[Laravel Preset] Allow App\Http to be used in providers
2026-02-17 15:31:01 +00:00
Nuno Maduro f96a1b2786 release: v4.4.1 2026-02-17 15:27:18 +00:00
Nuno Maduro a49cf7edc5 ci: speed up ci 2026-02-17 15:21:20 +00:00
Nuno Maduro b0f6a74cb6 ci: makes jobs faster 2026-02-17 15:18:33 +00:00
Nuno Maduro aaa226f6a6 chore: tests against symfony 8 2026-02-17 15:14:45 +00:00
Nuno Maduro 69cb752d02 chore: bumps dependencies 2026-02-17 15:01:37 +00:00
Nuno Maduro cf00e58b7d chore: bumps dependencies 2026-02-17 11:22:04 +00:00
Liam Hammett 1f39b28e2c Allow App\Http to be used in providers 2026-02-16 00:25:47 +01:00
nuno maduro 9fcbca69d4 Update README.md 2026-02-13 10:41:22 +00:00
dbpolito b081584ab6 Improvements 2026-02-11 18:09:09 -03:00
dbpolito 6966802afc Cleanup 2026-02-11 18:02:21 -03:00
dbpolito c61dcad42b Dataset Named Parameters 2026-02-11 17:57:07 -03:00
David B. ec3e0b2d33 fix: Parameter closure this type annotations in Functions.php 2026-02-09 20:48:56 +01:00
SimonBroekaert c3620840b4 feat: add '--only-covered' option to '--coverage' 2026-02-06 11:57:21 +01:00
vmadmax 10a19f16ba refactor: simplify regex to use Unicode properties \p{L} and \p{N 2026-02-02 09:41:54 +01:00
vmadmax a956de5446 fix: preserve Unicode characters in class names for --filter matching 2026-02-02 09:01:24 +01:00
Nuno Maduro 3a4329ddc7 release: 4.3.2 2026-01-28 01:01:19 +00:00
Ilia Smirnov e6f511302b fix: enhance support for --parallel and --teamcity arguments by restoring --teamcity for ParaTest and fixing teamcity output concurrency 2026-01-27 16:47:24 +01:00
nuno maduro dd01229d7b Merge pull request #1606 from smirok/teamcity-fix-for-describe-tests-with-dataset
fix: replace `substr` with `mb_substr` in Str::beforeLast to ensure multibyte string compatibility and correct TeamCity test names for datasets in "describe" blocks
2026-01-15 01:52:01 +00:00
Ilia Smirnov c7e4efcea4 fix: replace substr with mb_substr in Str::beforeLast to ensure multibyte string compatibility and correct TeamCity test names for datasets in "describe" blocks 2026-01-14 00:55:35 +01:00
nuno maduro df3205e814 Merge pull request #1554 from pindab0ter/feature/extend-closure-this
Specify closure this for extend
2026-01-13 01:19:47 +00:00
Nuno Maduro bc57a84e77 release: 4.3.1 2026-01-04 11:29:59 -05:00
nuno maduro bc39830d8a chore: removes toHaveSuspiciousCharacters from php preset 2026-01-04 11:25:57 -05:00
nuno maduro 3a566b100e docs: why php 2026-01-04 11:04:03 -05:00
nuno maduro 9fe61e0e56 docs: update sponsors
Removed and updated sponsor links in the README.
2026-01-03 18:07:02 -05:00
Nuno Maduro e86bec3e68 release: 4.3.0 2025-12-30 14:48:33 -05:00
nuno maduro 58b8f3cc5d Merge pull request #1598 from Willem-Jaap/willem-jaap/pest-only-file-level
feat: add pest only function to mark each test in a file as only
2025-12-30 14:40:04 -05:00
Willem-Jaap c157b661f2 style: format 2025-12-30 09:26:35 +01:00
Willem-Jaap be90610f17 feat: add pest only function to mark each test in a file as only 2025-12-30 09:24:05 +01:00
nuno maduro 1701a306c3 Merge pull request #1590 from pestphp/feature/intl-exception
feat: show more useful exception when `intl` extension not found
2025-12-29 22:09:16 -05:00
nuno maduro 064ab3fc2e Merge pull request #1595 from bilboque/dirty-flag-not-found-in-help
feat: add --dirty documentation in --help
2025-12-29 22:08:12 -05:00
leo 44e315df98 feat: add --dirty documentation in --help 2025-12-22 11:02:28 +01:00
Nuno Maduro 62694c14b9 chore: style 2025-12-15 11:54:24 +00:00
nuno maduro 7c43c1c583 Merge pull request #1586 from jackbayliss/bump-checkout-action
[4.x] Bump checkout version from 5 to 6
2025-12-15 11:49:28 +00:00
Nuno Maduro 6a96aed654 feat: adds phpunit@12.5 support 2025-12-15 11:48:43 +00:00
Owen Voke b1c997a869 feat: show more useful exception when intl extension not found 2025-12-12 12:02:00 +00:00
Jack Bayliss b4172e2c2e bump checkout version from 5 to 6 2025-12-10 14:08:06 +00:00
Nuno Maduro ae419afd36 chore: support for symfony 8.0.0 components 2025-11-28 12:04:48 +00:00
nuno maduro 27aa305897 Merge pull request #1576 from Chris53897/feature/ci
ci: bump actions/checkout 4 => 5
2025-11-25 11:18:28 +00:00
Bakhromjon 0e7c2abe8b Add Rules to Laravel preset 2025-11-25 15:32:36 +05:00
Nuno Maduro f5820bd670 release: 4.1.5 2025-11-24 12:46:38 +00:00
Nuno Maduro 41fd831153 release: 4.1.4 2025-11-24 10:25:45 +00:00
Christopher Georg 51340439e8 ci: bump actions/checkout 4 => 5 2025-11-22 12:12:15 +01:00
Nuno Maduro 1a39826935 ci: tests against php 8.5 2025-11-20 02:59:27 +00:00
Nino bd5fed9e12 add missing classes before toExtend on laravel preset
Add missing `->classes()` before `->toExtend()` on the laravel preset for 2 namespaces.

Otherwise you can't use interfaces on theses namespace.

For example, if you create an interface `YourSuperRequestContract` on the `app/Http/Requests` namespace you will get this error:

```
Expecting 'app/Http/Requests/YourSuperRequestContract.php' to extend 'Illuminate\Foundation\Http\FormRequest'.
```
2025-11-10 15:26:56 +00:00
Louis 26345fd9f4 fix: dataset inheritance with method chaining (beforeEach()->with(), describe()->with())
Fixes issue where datasets were not applied when using method chaining patterns
like beforeEach()->with([...]) or describe()->with([...]) inside nested describe blocks.

Root cause: Multiple functions were using Backtrace::file() which returns the
immediate caller's filename. This breaks when called through method chaining
because the backtrace returns internal Pest files instead of the test file.

Solution: Use Backtrace::testFile() which walks the entire backtrace to find
the actual test file being executed. This matches the pattern already used by
test() and describe() functions.

Changes in src/Functions.php:
- beforeEach(): Use testFile() to fix beforeEach()->with() pattern
- afterEach(): Use testFile() for consistency with beforeEach()
- beforeAll(): Use testFile() for better error messages
- afterAll(): Use testFile() for better error messages
- pest(): Use testFile() to fix pest()->beforeEach() pattern
- uses(): Use testFile() for consistency with pest()
- covers(): Use testFile() for correct test file context
- mutates(): Use testFile() for correct test file context

Changes in src/PendingCalls/DescribeCall.php:
- __destruct(): Force BeforeEachCall destructor before test creation
- __call(): Use $this->filename instead of Backtrace, more efficient
- __call(): Properly merge describing context for nested describe blocks

Fixes patterns:
- beforeEach()->with([...])
- describe()->with([...])
- pest()->beforeEach()->with([...]

Tests passing:
- tests/Features/Describe.php (all dataset tests)
- tests/Hooks/BeforeEachTest.php (global hook execution)
- tests/Features/Expect/toMatchSnapshot.php (28 tests)
2025-11-05 17:46:52 +01:00
Michael Withagen ae1da79ac1 Pass test dir to worker
#1444 
Test directory argument is lost when spawning workers, add it again.
2025-11-05 17:15:51 +01:00
nuno maduro 00990efc97 Merge pull request #1544 from Se7en-RU/fix-testdox-columns-warning
Fix Undefined array key "testdox-columns" warning
2025-11-04 07:42:57 +00:00
Nuno Maduro 477d20a54f release: 4.1.3 2025-10-29 22:45:27 +00:00
Hans van Luttikhuizen-Ross b7b16096db Specify closure this for extend 2025-10-29 11:20:08 +01:00
Yury Kudryavtsev 4105e33c39 Fix Undefined array key "testdox-columns" warning
Fix Undefined array key "testdox-columns" warning
2025-10-21 11:44:55 +03:00
Nuno Maduro 08b09f2e98 release: 4.1.2 2025-10-05 20:09:49 +01:00
Nuno Maduro b0fab7e437 chore: uses phpunit@12.4 2025-10-05 20:09:42 +01:00
Nuno Maduro 8e3444e1db chore: bumps requirements 2025-10-01 14:30:25 +01:00
Malico dc9a1e8ace BugFix: Fix toUseTrait to detect inherited and nested traits 2025-09-20 19:06:23 +01:00
nuno maduro fc7a4182b5 adjusts sponsors 2025-09-18 20:13:01 +01:00
Nuno Maduro b7406938ac release: v4.1.0 2025-09-10 14:41:09 +01:00
Nuno Maduro 314caabd1d chore: improves types 2025-09-10 14:41:02 +01:00
Nuno Maduro 65cabf91b1 chore: bumps dependencies 2025-09-10 14:40:52 +01:00
nuno maduro f91c6c1e1e Update social media links in Thanks.php 2025-09-01 00:09:31 +01:00
nuno maduro 843dbbf18a Update README.md 2025-08-31 23:50:16 +01:00
Nuno Maduro 47fb1d7763 release: v4.0.4 2025-08-28 19:19:42 +01:00
Nuno Maduro 639df4cb43 chore: uses phpunit 12.3.7 2025-08-28 19:19:32 +01:00
Nuno Maduro e54e4a0178 release: 4.0.3 2025-08-24 15:17:23 +01:00
Nuno Maduro 7749775f50 chore: uses phpunit 12.3.6 2025-08-24 15:17:19 +01:00
nuno maduro f11f3aa0a4 Merge pull request #1464 from gehrisandro/sandro/prevent-duplicate-attributes
Prevent duplicate attributes
2025-08-23 22:43:15 +01:00
Sandro Gehri 33817013fe Prevent duplicate attributes 2025-08-23 12:35:32 +02:00
Nuno Maduro 7f11ace329 release: 4.0.2 2025-08-22 11:34:24 +01:00
Nuno Maduro 3d776f1f20 fix: revert reading coverage by chunks 2025-08-22 11:12:55 +01:00
Nuno Maduro d5ced0a5ca release: 4.0.1 2025-08-22 09:24:07 +01:00
Nuno Maduro af1e214be4 chore: bumps dependencies 2025-08-22 09:22:16 +01:00
nuno maduro 7f9b50974a Merge pull request #1460 from Admiral-Enigma/4.x
Cast "testdox-columns" to an int
2025-08-22 09:20:44 +01:00
Admiral-Enigma cd5272d8cc Cast "testdox-columns" to an int 2025-08-22 10:00:49 +02:00
Nuno Maduro a7b2039175 Merge branch '3.x' into 4.x 2025-08-20 20:14:15 +01:00
Nuno Maduro 72cf695554 release: 3.8.4 2025-08-20 20:12:42 +01:00
Nuno Maduro 50960a96e9 docs: adjusts release script 2025-08-20 15:30:59 +01:00
Nuno Maduro 507df757a1 release: 4.0.0 2025-08-20 15:29:23 +01:00
Nuno Maduro 8722b3fc3c docs: adjusts readme for 4.x 2025-08-20 15:03:36 +01:00
Nuno Maduro 19eca6e338 fix: skip windows for now 2025-08-20 14:50:15 +01:00
Nuno Maduro 6b523d6963 fix: puts back windows 2025-08-20 14:27:53 +01:00
Nuno Maduro a350545803 fix: windows check 2025-08-20 14:25:49 +01:00
Nuno Maduro 71c2e97c9f chore: bumps dependencies 2025-08-20 14:23:16 +01:00
Nuno Maduro 98a12012bf Merge branch '3.x' into 4.x 2025-08-20 14:22:03 +01:00
Nuno Maduro 027f4e4832 chore: bumps dependencies 2025-08-20 14:21:14 +01:00
Nuno Maduro 92523a6f39 chore: uses phpunit v12.3.5 2025-08-20 08:01:45 +01:00
Nuno Maduro ed38fb644f release: 4.0.0 rc 1 2025-08-20 07:58:51 +01:00
Nuno Maduro 39b66bf01d Merge branch '3.x' into 4.x 2025-08-20 07:04:50 +01:00
Nuno Maduro 165c879fe6 release: 3.8.3 2025-08-19 11:11:21 +01:00
Nuno Maduro 4c8bf4b2fd chore: uses phpunit v11.5.33 2025-08-19 11:11:10 +01:00
Nuno Maduro 1ee36f584d Merge branch '3.x' into 4.x 2025-08-15 17:12:37 +01:00
nuno maduro 1b0a846a81 Update README.md 2025-08-15 17:11:53 +01:00
Nuno Maduro e3e518747f release: beta 2 2025-08-11 15:45:37 +01:00
Nuno Maduro 0b96b8f630 Allows old version of phpunit for now 2025-08-06 13:17:17 +01:00
Nuno Maduro 711a60c2db release: beta 1 2025-08-05 17:44:12 +01:00
Nuno Maduro e7132fa012 fix: removes parse_str from security preset 2025-08-03 16:09:59 -06:00
Nuno Maduro 3b72bbd7fe fix: toMatchObject accept objects 2025-08-03 11:08:11 -06:00
Nuno Maduro 273edb864c chore: adjusts tests 2025-08-03 10:32:14 -06:00
Nuno Maduro fcb60f3c4a chore: improves wording 2025-08-03 09:57:52 -06:00
Nuno Maduro 91bb7589e2 fix: wording on exception 2025-08-03 09:31:31 -06:00
Nuno Maduro e524bf5f73 fix: filter by dataset name 2025-07-30 15:52:40 -06:00
nuno maduro 27414ce19f Merge pull request #1372 from soyuka/patch-1
throw exception instead of using Panic in the TestSuite.php
2025-07-30 18:27:13 +01:00
nuno maduro fbc9e704e2 Merge pull request #1424 from joelbutcher/fix/throw-if-tests-directory-does-not-exist
[3.x] Throw a fatal exception if the tests directory does not exist
2025-07-30 18:26:31 +01:00
Joel Butcher ee6b3ed062 fix: throw a fatal exception if the tests directory does not exist 2025-07-29 19:13:33 +01:00
Nuno Maduro 4c88590b89 feat: not.toHaveSuspiciousCharacters 2025-07-26 07:47:00 -06:00
Nuno Maduro 66e59efec6 Merge branch '3.x' into 4.x 2025-07-26 07:35:23 -06:00
Nuno Maduro f692be3637 chore: bumps dependencies 2025-07-26 07:34:25 -06:00
Nuno Maduro 127ad618d3 chore: style 2025-07-26 07:34:19 -06:00
nuno maduro da04ba62a8 Merge pull request #1287 from mortenscheel/patch-1
Add toNotIncludeSuspiciousCharacters() expectation
2025-07-26 04:30:22 +01:00
Nuno Maduro d187566e63 chore: updates snapshots 2025-07-25 21:29:26 -06:00
nuno maduro 3e86e158b2 Merge pull request #1304 from jshayes/fix/sibling-describe-blocks
Fix an issue with describe blocks with matching names
2025-07-26 04:26:19 +01:00
nuno maduro d6c6489e93 Merge branch '4.x' into fix/sibling-describe-blocks 2025-07-26 04:26:10 +01:00
nuno maduro ee70a3cfea Merge pull request #1319 from pestphp/allow-custom-arch-expectations
Allow custom arch expectations
2025-07-26 04:23:49 +01:00
Nuno Maduro 7a6f33f139 Merge branch '3.x' into 4.x 2025-07-25 21:20:15 -06:00
nuno maduro 55218bcf78 Merge pull request #1324 from bibrokhim/add-attributes-to-laravel-preset
Add Attributes to Laravel preset
2025-07-26 04:19:54 +01:00
Nuno Maduro e29302300f Merge branch '3.x' into 4.x 2025-07-25 21:17:48 -06:00
nuno maduro 2a47b514ec Merge pull request #1351 from cndrsdrmn/patch-1
fix: add ignoring clause for `App\Features\Concerns` on Laravel Preset
2025-07-26 04:17:21 +01:00
Nuno Maduro 222ed174bc style 2025-07-25 21:10:05 -06:00
Nuno Maduro 2aa32569f0 fix: adjust snapshots 2025-07-25 21:06:49 -06:00
nuno maduro 1d8d1a046f Merge pull request #1357 from drsdre/patch-1
toMatchArray/Object wrong field fix
2025-07-26 04:06:12 +01:00
nuno maduro 3d9ceb1cf2 Merge pull request #1373 from clementbirkle/3.x
fix: normalize snapshot paths for tests outside the main tests directory
2025-07-26 03:58:40 +01:00
nuno maduro 520a5fe29d Merge branch '4.x' into 3.x 2025-07-26 03:56:09 +01:00
Nuno Maduro de4409e368 fix: before all 2025-07-25 20:54:37 -06:00
Nuno Maduro 6d6e4e040f fix: wrong status code being used 2025-07-25 18:03:58 -06:00
Nuno Maduro aac08629f7 ci: removes testing against lowest 2025-07-23 08:26:12 +01:00
Nuno Maduro fe27012bbc style 2025-07-22 23:58:36 +01:00
Nuno Maduro f9901245f1 updates dependencies and snapshots 2025-07-22 23:51:32 +01:00
nuno maduro 21e22decf3 Merge pull request #1299 from FaSe22/handle-c-flag
fix: Pest ignores '-c' option
2025-07-22 23:12:07 +01:00
nuno maduro e513f76ea9 Merge pull request #1256 from mazesec/add-slugify-method
Add slugify Method to Str Class and toBeSlug Assertion to Expectation Class
2025-07-22 23:08:15 +01:00
Nuno Maduro be9c95e3bc feat: adds see 2025-07-22 23:06:43 +01:00
nuno maduro 9172721ce8 Merge pull request #1241 from mertasan/ide-reference
feat: Add `references` method for two-way test-source linking
2025-07-22 22:48:26 +01:00
Nuno Maduro 924dc016cc feat: skipLocally 2025-07-22 22:40:38 +01:00
Nuno Maduro f49b91ec0d fixes missing condition 2025-07-22 22:31:08 +01:00
Nuno Maduro 516ace85b4 fix: skipOnCI 2025-07-22 22:08:03 +01:00
Nuno Maduro f9814793dd feat: skipOnCI 2025-07-22 21:52:06 +01:00
Nuno Maduro 00572f5f8e feat: improves playwright 2025-07-22 11:39:34 +01:00
Nuno Maduro fb282b184e fix: return type 2025-07-21 13:32:03 +01:00
Nuno Maduro e0695a13cb feat: adds shell 2025-07-21 13:25:50 +01:00
nuno maduro 8f810bf2a2 Merge pull request #1408 from JonPurvis/remove-language-option
[4.x] Remove Language Option from Profanity Composer Script
2025-07-14 10:37:14 +01:00
nuno maduro 84636cee96 Merge branch '4.x' into remove-language-option 2025-07-14 10:37:08 +01:00
Nuno Maduro 0355119afc fix: snapshots feedback 2025-07-06 14:29:18 +01:00
Nuno Maduro 9d0410ee0b feat: adjusts only for browser debug 2025-07-06 13:45:33 +01:00
Nuno Maduro 0d148c2a67 chroe: bumps phpunit 2025-07-05 15:46:03 +01:00
Nuno Maduro 0f1e87c726 Adds output about sharding 2025-07-05 15:43:43 +01:00
Nuno Maduro 73bf579da3 chore: code refactor 2025-07-02 00:26:15 +01:00
Nuno Maduro 5def62018b fix: shard regex 2025-07-01 11:00:05 +01:00
Nuno Maduro d8e1b27491 ci: fix included version 2025-06-30 22:50:57 +01:00
Nuno Maduro 2ff4713968 ci: fix missing dep 2025-06-30 22:26:09 +01:00
Nuno Maduro 3f27352560 feat: adds --shard 2025-06-30 22:15:07 +01:00
Nuno Maduro af3fdceddb feat: adds phpunit 12.2.5 support 2025-06-28 18:31:45 +01:00
Nuno Maduro 3faeede1ef chore: fixes snapshots 2025-06-28 18:24:19 +01:00
Nuno Maduro 0bc3219a2b feat: moves visit to the core 2025-06-28 18:18:26 +01:00
Nuno Maduro a22013a7d3 fix: with types 2025-06-28 12:14:45 +01:00
Nuno Maduro 7fc69033f8 chore: adjusts style 2025-06-27 02:15:36 +01:00
Nuno Maduro ef76c04dbe feat: adds fixture 2025-06-27 02:15:28 +01:00
Nuno Maduro 7d77bbf1bb Merge pull request #1410 from JonPurvis/remove-period
Remove Period from `ShouldNotHappen` message
2025-06-23 19:29:01 +01:00
Nuno Maduro 163479ae60 chore: style 2025-06-16 10:14:16 +01:00
Nuno Maduro c3bfdf130e chore: type checking 2025-06-16 10:14:04 +01:00
Nuno Maduro 8c403a57c2 fix: upper case fix 2025-06-16 10:04:00 +01:00
JonPurvis 97c136cd94 link to issues page 2025-06-16 02:55:11 +01:00
JonPurvis d6cbd12d8b remove period from message 2025-06-16 02:51:48 +01:00
Nuno Maduro 49bf00024f fix: coverage when coverage file is over 2.4gb on mac os 2025-06-15 22:43:59 +01:00
JonPurvis dd44ac4195 remove language option from profanity composer script 2025-06-14 21:36:39 +01:00
Nuno Maduro 5d2aafd2a3 feat: --profanity 2025-06-12 00:38:05 +01:00
Nuno Maduro 0fc9d4dfe0 feat: adds phpunit 12.2.1 support 2025-06-08 15:29:23 +01:00
Nuno Maduro 02b1ffb334 chore: bump dependencies 2025-05-27 11:37:29 +01:00
Nuno Maduro c62cc3fef0 chore: adds pokio 2025-05-23 05:19:56 +01:00
Nuno Maduro 909d778da3 fix: undefined property 2025-05-21 02:00:15 +01:00
Nuno Maduro 7711a52fe9 Bumps dependencies 2025-05-09 13:10:29 +01:00
Nuno Maduro 99c9f4e5d8 Bumps dependencies 2025-05-03 11:58:03 +01:00
Nuno Maduro a310796165 Fixes filtering tests 2025-04-29 11:38:33 +01:00
Nuno Maduro db9243ca2e bump dependencies 2025-04-29 09:57:02 +01:00
Nuno Maduro 635e3b4c41 chore: deprecates php 8.2 2025-04-20 23:02:19 +01:00
Nuno Maduro 791734a29c Fixes tests 2025-04-20 22:19:25 +01:00
Nuno Maduro 8cfb0acf46 bump paratest 2025-04-20 21:51:01 +01:00
Nuno Maduro bf67407ba5 Merge pull request #1391 from MrPunyapal/feat/phpunit-12
[WIP] Feat: PHPUnit 12
2025-04-20 21:35:32 +01:00
Nuno Maduro efdc84e115 Merge branch '4.x' into feat/phpunit-12 2025-04-20 21:35:25 +01:00
Nuno Maduro d1608bf33d chore: prepares for 4.x 2025-04-20 21:33:50 +01:00
Punyapal Shah 4f6140fdb1 refactor: move test case initialization to a separate method in Testable trait 2025-04-20 15:37:02 +05:30
Punyapal Shah 442a58d07f refactor: comment arch presets in Arch.php 2025-04-20 15:19:56 +05:30
Punyapal Shah 19e9267021 fix: update PHPUnit version 2025-04-20 15:19:40 +05:30
Nuno Maduro c6244a8712 Release 3.8.2 2025-04-17 11:53:02 +01:00
Nuno Maduro eed68f2840 Adjusts sponsors 2025-04-13 17:15:23 +01:00
Nuno Maduro 6080f51a0b release: v3.8.1 2025-04-03 17:35:58 +01:00
Nuno Maduro e0f07be017 fix: init command detecting laravel 2025-04-03 17:23:39 +01:00
Nuno Maduro 42e1b9f17f release: v3.8.0 2025-03-30 18:49:10 +01:00
Nuno Maduro 0171617c1d chore: adjusts to new types on arch 2025-03-30 18:42:00 +01:00
Nuno Maduro 2e11e9e65d docs: adjusts readme 2025-03-29 18:23:23 +00:00
Nuno Maduro 4969526ef2 chore: bumps paratest 2025-03-29 17:57:53 +00:00
Nuno Maduro d7b1c36fdd Merge pull request #1341 from nuernbergerA/phpunit-overrides
chore: Sync overrides
2025-03-29 17:52:57 +00:00
Nuno Maduro 003fc96e8f release: 3.7.5 2025-03-29 17:48:00 +00:00
Nuno Maduro f68d11ccae chore: bumps dependencies 2025-03-29 17:44:06 +00:00
Clément Birklé e46d499384 add tests for snapshots external to the tests directory 2025-03-19 16:33:44 +01:00
Clément Birklé 490f321a0d fix: normalize snapshot paths for files outside tests directory 2025-03-19 15:59:45 +01:00
Antoine Bluchet 174645caa2 throw exception instead of using Panic 2025-03-18 14:49:32 +01:00
Nuno Maduro ed70c9dc2b refactor: type adjustments 2025-03-14 22:40:39 +00:00
Nuno Maduro 157a753d87 Update Pest.php.stub 2025-03-12 18:35:57 +00:00
AFS 2c3a53f6cd Remove reset of message
Reset approach was not the right one.
2025-02-25 15:44:32 +01:00
AFS 0bdaef29e9 Fix lingering }
Remove remaining } in toMatchObject.
2025-02-25 15:41:17 +01:00
AFS 1ad30a97b3 toMatchArray/Object wrong field fix
The functions toMatchArray and toMatchObject indicate that the wrong field is mismatching from the second loop on because the 'message' is overwritten and taken into the following loop. This patch creates a $second_message for the second test (value test) to keep the error message correct.
2025-02-25 15:34:37 +01:00
Candra Sudirman a5317c5640 fix: add ignoring clause for App\Features\Concerns on Laravel Preset 2025-02-08 00:07:21 +07:00
Nuno Maduro 66ceb64faa Updates tests 2025-02-03 13:36:47 +00:00
Nuno Maduro fa4098db8d Bumps dependencies 2025-02-03 13:30:45 +00:00
Nuno Maduro 4a987d3d5c release: 3.7.4 2025-01-23 14:03:29 +00:00
Nuno Maduro 4079a08f5f feat: adds --compact to coverage 2025-01-23 13:59:51 +00:00
Nuno Maduro e4aab77a34 release: 3.7.3 2025-01-23 12:51:02 +00:00
jshayes 50ff347b59 Pass description into describe call 2025-01-20 17:40:18 -05:00
jshayes b5b8fab09b Fix an issue where beforeEach/afterEach functions were called on other describe blocks with the same name 2025-01-20 17:36:22 -05:00
Adrian Nürnberger c4c9e915f4 cs 2025-01-20 09:50:36 +01:00
Adrian Nürnberger e834527db2 Update JunitXmlLogger.php
https://github.com/sebastianbergmann/phpunit/issues/6098
2025-01-20 09:39:10 +01:00
Adrian Nürnberger 23f130b0f9 Update JunitXmlLogger.php
from https://github.com/sebastianbergmann/phpunit/issues/5771
https://github.com/sebastianbergmann/phpunit/commit/c722fb259972a6c55fbb295169fd1a000b3d4c88
2025-01-20 09:38:24 +01:00
Adrian Nürnberger 0cb8c42497 sync missing listener 2025-01-20 09:36:48 +01:00
Adrian Nürnberger fe4b5e5e1f sync change 2025-01-20 09:35:44 +01:00
Adrian Nürnberger 8ee9d66d80 sync cs 2025-01-20 09:34:55 +01:00
Adrian Nürnberger 7760d945bb sync latest changes 2025-01-20 09:34:23 +01:00
Nuno Maduro 709ecb1ba2 chore: adjusts tests 2025-01-19 17:35:09 +00:00
Nuno Maduro 6afb36519d release: 3.7.2 2025-01-19 17:16:25 +00:00
Nuno Maduro 150bb9478d docs: adjusts sponsors 2025-01-08 01:09:20 +00:00
Nuno Maduro bf3178473d release: 3.7.1 2024-12-12 11:52:01 +00:00
Nuno Maduro d2eb94d723 chore: bumps phpunit and paratest 2024-12-12 11:50:43 +00:00
Nuno Maduro 9688b83a3d release: 3.7.0 2024-12-10 11:54:49 +00:00
Nuno Maduro 675372c794 chore: fixes types 2024-12-10 11:54:42 +00:00
Nuno Maduro c18636b3d5 chore: adds phpunit 11.5.0 support 2024-12-10 11:53:59 +00:00
Bakhromjon 1ac594bdf0 Add Attributes to Laravel preset 2024-12-06 16:07:59 +05:00
Nuno Maduro 145294a4a3 chore: style 2024-12-01 23:55:15 +00:00
Nuno Maduro c2cabaeae6 chore: fixes test suite 2024-12-01 23:16:34 +00:00
Nuno Maduro 918a8fc169 release: 3.6.0 2024-12-01 22:46:00 +00:00
Nuno Maduro 5d32dd0641 feat: option to coverage 2024-12-01 22:45:31 +00:00
Nuno Maduro 982353fb38 release: 3.5.2 2024-12-01 21:28:14 +00:00
Nuno Maduro 2eefa8b88d chore: adds phpunit 11.4.4 support 2024-12-01 21:26:11 +00:00
Nuno Maduro 787d5492ac chore: ignores temporary file 2024-12-01 20:41:34 +00:00
Nuno Maduro 06a0bd9b0b Bumps dependencies 2024-11-21 10:46:27 +00:00
olivernybroe 5331b44a18 Allow custom arch expectations 2024-11-20 11:54:36 +01:00
Nuno Maduro 91afc81222 Updates sponsors 2024-11-09 14:20:12 +00:00
Nuno Maduro 179d46ce97 release: v3.5.1 2024-10-31 12:12:45 -04:00
Nuno Maduro fa2bc1e536 chore: bumps dependencies 2024-10-31 12:12:37 -04:00
Nuno Maduro eaeb133c77 release: v3.5.0 2024-10-22 15:33:27 +01:00
Nuno Maduro cf57ea1f94 Merge pull request #1295 from jshayes/nested-describe
Support for nested describe blocks
2024-10-22 13:41:38 +01:00
Nuno Maduro 0b7f4f2384 Merge pull request #1301 from faissaloux/update-tests-badge
Update tests badge to v3
2024-10-20 12:48:07 +01:00
Nuno Maduro 2903a7e621 release: v3.4.2 2024-10-20 12:47:25 +01:00
Nuno Maduro b8964375c7 release: v3.4.1 2024-10-20 12:43:35 +01:00
Nuno Maduro bdcb883829 chore: bumps phpunit version 2024-10-20 12:43:28 +01:00
faissaloux 8a7e7f39ef update tests badge to v3 2024-10-17 02:33:25 +01:00
sebastianfaber 53c94600cb fix: handle -c flag same as --configuration 2024-10-16 22:19:08 +02:00
Nuno Maduro 67f217852c chore: uses stable version of collision and termwind 2024-10-15 17:17:09 +01:00
Nuno Maduro 1bad148487 release: v3.4.0 2024-10-15 15:13:19 +01:00
Nuno Maduro e24f137b8e fix: deprecation 2024-10-15 15:09:26 +01:00
Nuno Maduro 6d9189f3f5 feat: php 8.4 support 2024-10-15 15:04:30 +01:00
jshayes 6968094e2b Add tests 2024-10-13 10:39:17 -04:00
jshayes 9510d4a2f9 Change array type hint 2024-10-13 09:54:19 -04:00
jshayes cd2eb3504b Add helper to get last element of array 2024-10-13 09:47:54 -04:00
jshayes 7c639cdbbd Add more tests 2024-10-13 00:41:04 -04:00
Nuno Maduro 1513ede73b release: v3.3.2 2024-10-12 12:36:44 +01:00
Nuno Maduro 8c65197881 chore: bumps depndencies 2024-10-12 12:33:57 +01:00
jshayes a6cd83665c Execute all parent beforeEach and afterEach functions for each test 2024-10-11 23:51:18 -04:00
jshayes 0c57142c03 Fix an issue where a describe block will prevent a beforeEach call from executing 2024-10-11 21:24:08 -04:00
Nuno Maduro 3f65af9fdf Merge pull request #1292 from olivernybroe/policy-suffix
Add Policy suffix to policies
2024-10-11 09:34:05 +01:00
Nuno Maduro 42d89814e3 Merge pull request #1293 from AbdellahBoutmad/dir
modify test command in contrbuting.md
2024-10-11 09:33:41 +01:00
Nuno Maduro 1e3156a5b6 release: v3.3.1 2024-10-11 09:31:24 +01:00
Nuno Maduro 97713c0832 chore: bumps dependencies 2024-10-11 09:31:16 +01:00
abdellah boutmad 62b0e3c9df modify test command in contrbuting.md 2024-10-10 12:34:04 +01:00
olivernybroe 647de2f1cf Add Policy suffix to policies 2024-10-10 08:08:10 +02:00
Nuno Maduro 0a7bff0d24 release: v3.3.0 2024-10-06 19:25:27 +01:00
Nuno Maduro 7618434580 chore: uses phpunit v11.4.0 2024-10-06 19:25:20 +01:00
Morten Scheel dd7d150caa Add toNotIncludeSuspiciousCharacters() expectation 2024-10-04 13:44:10 +02:00
Nuno Maduro 1e0bb88b73 release: v3.2.5 2024-10-01 11:55:18 +01:00
Nuno Maduro 83b76d7c2e chore: bumps dependencies 2024-10-01 11:51:26 +01:00
Nuno Maduro 5a870b3940 chore: style changes 2024-10-01 11:51:16 +01:00
Nuno Maduro 1115c64186 chore: style changes 2024-10-01 11:48:14 +01:00
Nuno Maduro e38a271ca2 Merge pull request #1279 from midnite81/bug/declare-strict-types-with-comments-above
Strict types expectation allows for comments above declaration
2024-09-29 19:12:06 +01:00
Nuno Maduro 43703ab40a Merge pull request #1280 from CamKem/fix/middleware-method-preset
Fix: Add middleware to the allowable public methods for Laravel Preset
2024-09-29 19:11:40 +01:00
Cam Kemshal-Bell 86452765a4 fix: add middleware to the allowable public methods on the laravel preset 2024-09-29 16:07:39 +10:00
Simon Rogers b8a1b7e5cc Add tests for strict types expectation
Introduced new test cases to ensure strict type declaration handling. Files with and without strict types are tested, including scenarios with comments preceding the declaration. Updated the regex in `Expectation.php` to accommodate comments and whitespaces before the `declare(strict_types=1)` statement.
2024-09-28 17:31:33 +01:00
Nuno Maduro 5fe79d9c18 release: v3.2.4 2024-09-26 23:53:39 +01:00
Nuno Maduro 2744da4292 Merge pull request #1277 from MuhammedAlkhudiry/ignore-handler-in-laravel-preset
ignore App\Exceptions\Handler.php in arch laravel preset
2024-09-26 23:47:26 +01:00
Muhammed Alkhudiry 87f4e5e7b3 fix 2024-09-26 16:44:30 +03:00
Muhammed Alkhudiry bb3decf3cc ignore App\Exceptions\Handler.php in arch laravel preset 2024-09-26 16:41:43 +03:00
Nuno Maduro 4e2987d438 release: v3.2.3 2024-09-25 16:19:39 +01:00
Nuno Maduro a25158bce8 Merge pull request #1275 from jeremynikolic/laravel-presets-ignore-concerns
feat: add ignoring of Concerns folder inside App\Enums and App\Features
2024-09-25 16:16:26 +01:00
Jeremy Nikolic 49e77b1d4c feat: add ignoring of Concerns folder inside App\Enums and App\Features 2024-09-25 17:12:42 +02:00
Nuno Maduro 989e43d1a0 release: v3.2.2 2024-09-24 10:23:43 +01:00
Nuno Maduro 7cd42aafd8 fix: auto-complete on presets 2024-09-24 10:23:32 +01:00
Nuno Maduro 48a1de273f release: v3.2.1 2024-09-23 14:09:55 +01:00
Nuno Maduro 970e16e949 Ignores 2024-09-23 14:08:30 +01:00
Nuno Maduro 432ff221c6 fix: missing != and !== on new toUseStrictEquality arch expectation 2024-09-23 14:08:21 +01:00
Nuno Maduro a55da85dd2 release: v3.2.0 2024-09-23 13:14:03 +01:00
Nuno Maduro f291cd1603 chore: bumps dependencies 2024-09-23 13:11:49 +01:00
Nuno Maduro 5de0c2254a release: v3.1.0 2024-09-19 23:39:07 +01:00
Nuno Maduro b98ce0ced3 feat: adds mutates 2024-09-19 23:32:28 +01:00
Nuno Maduro 28772c2609 chore: dont run integration tests yet on php 8.4 2024-09-19 13:42:01 +01:00
Nuno Maduro 452ffaf8df chore: fixes windows build 2024-09-19 13:38:35 +01:00
Nuno Maduro e8338405b5 chore: tests against PHP 8.4 2024-09-19 13:36:41 +01:00
Nuno Maduro 1b014e4b18 release: v3.0.8 2024-09-19 13:04:42 +01:00
Nuno Maduro 034715e8b1 Merge pull request #1266 from julien-boudry/3.x
Fix #1265 -  issue parameter cannot be int (one done, pr, todo, wip)
2024-09-19 12:53:34 +01:00
Nuno Maduro 09eff785c4 release: v3.0.7 2024-09-19 12:29:38 +01:00
Nuno Maduro 22cc7805d7 chore: bumps dependencies 2024-09-19 12:29:38 +01:00
Julien Boudry 669dc0da71 Fix #1265 - issue parameter cannot be int (one done, pr, todo, wip) 2024-09-19 09:49:36 +00:00
Nuno Maduro 689da4ed4e Merge pull request #1254 from pestphp/bugfix/jira-url
fix: update assignee URL for Jira
2024-09-18 21:48:09 +01:00
tal7aouy 92bc1decd9 Add tests for toBeSlug method 2024-09-16 13:41:13 +01:00
tal7aouy e3bfcbe5f1 Add slugify method 2024-09-16 13:36:34 +01:00
Owen Voke 2f15861b0d fix: update assignee URL for Jira 2024-09-16 12:18:21 +01:00
Mert Aşan ba7eb70a5d Remove unnecessary property 2024-09-12 13:11:12 +03:00
Mert Aşan 74ff3b8cd9 Update src/PendingCalls/TestCall.php
Co-authored-by: Owen Voke <development@voke.dev>
2024-09-12 13:09:57 +03:00
Mert Aşan ab0b4a1b4e Update src/PendingCalls/TestCall.php
Co-authored-by: Owen Voke <development@voke.dev>
2024-09-12 13:09:45 +03:00
Mert Aşan 169b76458e make the name of the method plural 2024-09-12 11:15:30 +03:00
Mert Aşan 668685498f Fix phpdoc type-hints 2024-09-12 03:51:20 +03:00
Mert Aşan bab193e7e1 Fix property type 2024-09-12 03:24:36 +03:00
Mert Aşan f720be862e Add reference method 2024-09-12 02:45:37 +03:00
Nuno Maduro 0d50d35b5e release: v3.0.6 2024-09-11 18:59:43 +01:00
Nuno Maduro ce61ced8e1 Merge pull request #1237 from smirok/teamcity-fix-for-tests-with-dataset
fix: unify the `locationHint` prefix and prettify both `locationHint` and `name` parameters for testing with datasets
2024-09-11 18:51:04 +01:00
Ilia Smirnov 7227d24611 fix: unify the locationHint prefix and prettify both locationHint and name parameters for testing with datasets 2024-09-11 16:42:06 +02:00
Nuno Maduro 45f16484d5 Merge pull request #1235 from pestphp/3.x_herd_fix
Fixes parallel mutation testing when using Laravel Herd
2024-09-11 15:13:49 +01:00
lukeraymonddowning b16e8650da Fixes parallel mutation testing when using Laravel Herd. 2024-09-11 15:11:47 +01:00
lukeraymonddowning c2f30e0148 Fixes parallel mutation testing when using Laravel Herd. 2024-09-11 15:04:44 +01:00
Nuno Maduro 47ce45de56 release: v3.0.4 2024-09-11 00:48:29 +01:00
Nuno Maduro 32881774d2 fix: global afterEach being called twice 2024-09-11 00:40:41 +01:00
Nuno Maduro ea72461f1b release: v3.0.3 2024-09-10 22:29:09 +01:00
Nuno Maduro 49f15521e0 fix: printer method name 2024-09-10 22:29:01 +01:00
Nuno Maduro 95c5394b66 Bumps dependencies 2024-09-10 16:59:38 +01:00
Nuno Maduro 8de30cc8b7 release: v3.0.1 2024-09-09 15:29:44 +01:00
Nuno Maduro b17feef3f4 release: v3.0.0 2024-09-09 11:01:08 +01:00
Nuno Maduro d8e4a405ad chore: bumps dependencies 2024-09-09 10:57:31 +01:00
Nuno Maduro 04af21183a chore: fixes style 2024-09-09 10:53:45 +01:00
Nuno Maduro 09edaa9c2d chore: updates snapshots 2024-09-09 10:49:48 +01:00
Nuno Maduro fa41a67be9 chore: fixes type checking 2024-09-09 10:37:20 +01:00
Nuno Maduro 1a8f7025fa release: v3.0.0 2024-09-09 01:20:30 +01:00
Nuno Maduro 6afd2ec9df release: v3.0.0 2024-09-09 01:16:11 +01:00
Nuno Maduro d772069db2 release: v3.0.0 2024-09-09 01:12:41 +01:00
Nuno Maduro bb1a0b5e79 chore: locks phpunit 2024-09-09 01:12:33 +01:00
Nuno Maduro 3c333ebbb8 docs: updates banner 2024-09-09 01:10:33 +01:00
Nuno Maduro 868ac1840f chore: bumps dependencies 2024-09-08 23:59:12 +01:00
Nuno Maduro f857b4889c fix: load of mutate 2024-09-08 23:56:48 +01:00
Nuno Maduro c6b81e6e12 More fixes 2024-09-06 20:59:54 +01:00
Nuno Maduro c78d33b69e wip 2024-09-05 23:37:40 +01:00
Nuno Maduro bfd351783e wip 2024-09-05 22:41:26 +01:00
Nuno Maduro 526af2a75e wp 2024-09-05 21:56:51 +01:00
Nuno Maduro bf9d011045 Merge pull request #1220 from gehrisandro/fix/accept-traits-in-covers-function 2024-09-05 21:41:53 +01:00
Sandro Gehri aaee0e420b Fix covers function to accept traits 2024-09-05 22:36:34 +02:00
Nuno Maduro 772448db80 wip 2024-09-05 20:53:10 +01:00
Nuno Maduro e22fb2e6c0 w 2024-09-05 19:48:03 +01:00
Nuno Maduro 49aa44c470 w 2024-09-05 19:43:20 +01:00
Nuno Maduro 1cae035887 fix 2024-09-05 18:26:13 +01:00
Nuno Maduro 15183c4145 fix 2024-09-05 18:23:57 +01:00
Nuno Maduro ae288d1123 fix tests 2024-09-05 18:17:30 +01:00
Nuno Maduro 2d80ff19ec feat(mutate): only 2024-09-05 02:49:52 +01:00
Nuno Maduro c82f77ea75 fix 2024-09-05 02:05:28 +01:00
Nuno Maduro 5050ae304f fixes tests 2024-09-05 01:24:55 +01:00
Nuno Maduro 98e947e0cc more fixes 2024-09-05 01:19:17 +01:00
Nuno Maduro 68785986a0 fix 2024-09-05 00:55:37 +01:00
Nuno Maduro 8c078087ff Adds covered classes to mutation 2024-09-05 00:14:12 +01:00
Nuno Maduro 65f74f620c feat: adds covers 2024-09-05 00:10:29 +01:00
Nuno Maduro dd20323ca7 feat: custom presets 2024-09-04 20:53:33 +01:00
Nuno Maduro a7ca7afe4e fix: preset laravel 2024-09-04 11:51:01 +01:00
Nuno Maduro baf764f286 feat(mutate): requires it by default 2024-09-04 11:38:59 +01:00
Nuno Maduro 3a907c886b feat(mutate): requires it by default 2024-09-04 11:38:53 +01:00
Nuno Maduro e6823679dd feat: adds not->toHavePrivateMethods and related 2024-09-04 11:18:41 +01:00
Nuno Maduro a021b5b8c3 feat(laravel-preset): traits in traits folders 2024-09-04 11:17:32 +01:00
Nuno Maduro e2d360b1b5 chore: adjusts tests 2024-09-03 14:09:03 +01:00
Nuno Maduro 8920b850e1 feat(arch-presets): adds another rule to laravel preset 2024-09-03 14:08:11 +01:00
Nuno Maduro 509074b3fa chore: bumps dependencies 2024-09-03 14:07:56 +01:00
Nuno Maduro 6f9ea14c68 Merge pull request #1215 from jbrooksuk/patch-1
Add `dump` to Laravel preset
2024-09-03 12:17:01 +01:00
Nuno Maduro 60dcfb36a8 Merge pull request #1207 from JonPurvis/coverage-fix
Fix coverage result for 99.95%+
2024-09-03 12:16:37 +01:00
Nuno Maduro ca25d5b13f Merge pull request #1208 from benjamincrozat/patch-1
Exclude Global Scopes from the required "Model" suffix
2024-09-03 12:15:59 +01:00
Nuno Maduro 5cba63e2ba Merge pull request #1209 from phh/patch-1
chore: Use new rector syntax
2024-09-03 12:15:43 +01:00
James Brooks dd45a5c655 Add dump to Laravel preset 2024-09-03 12:15:25 +01:00
Nuno Maduro dde943b993 refacto: toHaveMethod and toHaveMethods 2024-09-02 19:38:38 +01:00
Nuno Maduro bb8677549a chore: fixes snapshots 2024-09-02 18:30:12 +01:00
Nuno Maduro 5ae5ac9a54 fix: removes both toHaveMethod and toHaveMethods 2024-08-28 16:30:29 -05:00
Patrick Hesselberg cc6f1b43f6 chore: Remove InlineConstructorDefaultToPropertyRector rule
Since its already a part of the codeQuality ruleset.
2024-08-28 08:26:47 +02:00
Patrick Hesselberg 88197fe1d5 chore: use new rector syntax 2024-08-28 08:22:18 +02:00
Benjamin Crozat f53f855e9c Exclude scopes from the required "Model" suffix 2024-08-27 19:57:09 +02:00
JonPurvis df69b0b791 fix coverage result for 99.95%+ 2024-08-26 21:58:16 +01:00
Nuno Maduro ecdbe7a472 release: 3.0.0-rc.1 2024-08-22 21:36:19 +01:00
Nuno Maduro 2c6c3119d2 Merge branch '2.x' into 3.x 2024-08-22 21:21:52 +01:00
Nuno Maduro 9ceb0834ae chore: updates snapshots 2024-08-22 21:07:39 +01:00
Nuno Maduro 86d2191cae chore: refactor TestClosureMustNotBeStatic 2024-08-22 20:59:42 +01:00
Nuno Maduro 748beb17d5 chore: adjusts memory limit 2024-08-22 20:59:42 +01:00
Nuno Maduro 7ba235f61a Merge pull request #1129 from tomb1n0/bugfix/alwaysCallTeardownRegardlessOfExceptions
[Bug] Always call parent teardown even if an exception is thrown
2024-08-22 20:50:59 +01:00
Nuno Maduro 700bd517f4 Merge pull request #1117 from peterfox/bug/catch-static-closures
[Bug] provided explaination for static closures
2024-08-22 20:33:58 +01:00
Nuno Maduro cbcfa2c5e2 Merge pull request #1100 from faissaloux/fix-use-strict-types
Fix `toUseStrictTypes`
2024-08-22 20:32:21 +01:00
Nuno Maduro 243e45a551 Merge pull request #1097 from arifszn/2.x
[2.x] Modify `Result::exitCode` logic to address warning handling with `--fail-on-warning`
2024-08-22 20:31:34 +01:00
Nuno Maduro 9b11857ae6 chore: updates snpahosts 2024-08-20 22:59:25 +01:00
Nuno Maduro bb29e97200 Merge branch '2.x' into 3.x 2024-08-20 22:57:52 +01:00
Nuno Maduro 8fe2698c28 chore: fixes test suite 2024-08-20 22:57:10 +01:00
Nuno Maduro 823c3d4b17 chore: updates snapshots 2024-08-20 22:49:23 +01:00
Nuno Maduro 39c9b15bc0 Update visual_snapshot_of_help_command_output.snap 2024-08-20 22:44:39 +01:00
Nuno Maduro b13acb630d release: 2.35.1 2024-08-20 22:41:50 +01:00
Nuno Maduro 172d94c0ca chore: bumps depedencies 2024-08-20 22:41:07 +01:00
Nuno Maduro f72d6f2278 Removes PHP 8.4 2024-08-20 22:39:00 +01:00
Nuno Maduro 71811d6e3a Bumps PHP version 2024-08-20 22:37:34 +01:00
Nuno Maduro dfdbd357e9 chore: bumps dependencies 2024-08-20 22:37:27 +01:00
Nuno Maduro 4d9ed8768c fix: panic with TestDescriptionMissing 2024-08-19 02:24:18 +01:00
Nuno Maduro 6638d279e1 preset(strict): final first 2024-08-12 21:39:41 +01:00
Nuno Maduro b5cd0ffb65 chore: updates snapshots 2024-08-12 01:13:33 +01:00
Nuno Maduro 7ef40760c2 fixes filter 2024-08-12 00:46:31 +01:00
Nuno Maduro ce4495b093 w 2024-08-11 23:41:19 +01:00
Nuno Maduro 868547114f w 2024-08-11 22:52:16 +01:00
Nuno Maduro 9c07dd9990 wip 2024-08-11 15:47:48 +01:00
Nuno Maduro 09beb812d4 fix 2024-08-10 17:27:21 +01:00
Nuno Maduro 4e98dfe3c3 pr 2024-08-10 16:57:48 +01:00
Nuno Maduro ad6dca94fa Adds done 2024-08-10 14:27:18 +01:00
Nuno Maduro 86f46c2efd Adds alias 2024-08-10 13:56:40 +01:00
Nuno Maduro ccfd4fd77a beta 2 2024-08-10 11:49:44 +01:00
Nuno Maduro e4d2dac354 beta 2 2024-08-10 11:49:40 +01:00
Nuno Maduro 7e4c51e13d Style 2024-08-10 11:48:32 +01:00
Nuno Maduro aacd874ebe fix 2024-08-09 00:38:16 +01:00
Nuno Maduro 1c236aab26 fixes and styles 2024-08-09 00:30:06 +01:00
Nuno Maduro b6bf01148f feat: toHavePrivateMethodsBesides, toHaveProtectedMethodsBesides, toHavePublicMethodsBesides 2024-08-09 00:24:24 +01:00
Nuno Maduro 347bcfd8a8 Bumps dependencies 2024-08-09 00:24:09 +01:00
Nuno Maduro 0ced3171b0 fix: missing methods 2024-08-07 11:30:19 +01:00
Nuno Maduro 38638e865f chore: fixes tests 2024-08-07 11:27:30 +01:00
Nuno Maduro adbc6b4a89 chore: fixes tests 2024-08-07 11:26:56 +01:00
Nuno Maduro 9353015691 feat(presets): reworks code 2024-08-07 11:08:29 +01:00
Nuno Maduro 17058d1709 chore: bumps dependencies 2024-08-07 11:06:41 +01:00
Nuno Maduro 8ffa66dc7c fix(autoloader): issue when errors when loading the file 2024-08-07 11:06:34 +01:00
Nuno Maduro 0697555dc2 chore: adjusts sponsors 2024-08-05 10:42:52 +01:00
Nuno Maduro af680ca8aa feat(todos): adjusts params order 2024-08-03 18:48:53 +01:00
Nuno Maduro 651aab560c Allows to set context on todos 2024-08-03 18:42:31 +01:00
Nuno Maduro 41e50cac05 Adds assignees 2024-08-03 17:05:34 +01:00
Nuno Maduro 6fb1133d52 Adjusts for Collision 8.4 2024-08-03 16:36:01 +01:00
Nuno Maduro 63ba117b33 Fixes tests 2024-08-03 00:13:01 +01:00
Nuno Maduro 33d36d77cb Merge branch '2.x' into 3.x 2024-08-02 23:53:26 +01:00
Nuno Maduro 4e7db91ee8 chore: updates dependencies 2024-08-02 23:46:31 +01:00
Nuno Maduro d0ff2c8ec2 release: 2.35.0 2024-08-02 11:57:29 +01:00
Nuno Maduro 5e41e546a0 chore: style changes 2024-08-02 11:53:24 +01:00
Nuno Maduro 45cce6ce93 Style 2024-07-24 21:54:52 +01:00
Nuno Maduro 6a8a4f3243 Merge pull request #1194 from dmason30/patch-1
Include method name in toHaveMethod error message
2024-07-20 18:29:37 +01:00
Nuno Maduro 101e26749a fix: properties and methods documented 2024-07-20 17:45:41 +01:00
Nuno Maduro b3c8c24aea Reworks 2024-07-20 14:15:28 +01:00
Dan Mason ef29b4f091 Include method name in toHaveMethod error message 2024-07-19 15:30:43 +01:00
Nuno Maduro a7553b7593 feat(to-have-all*): improvements 2024-07-19 14:32:46 +01:00
Nuno Maduro f2691623cf feat: toHaveAllMethodsDocumented and toHaveAllPropertiesDocumented 2024-07-19 14:03:59 +01:00
Nuno Maduro 99107544ff chore: fixes types 2024-07-18 20:45:47 +01:00
Nuno Maduro 2e411893d2 feat: adds toHaveLineCountLessThan 2024-07-18 20:43:39 +01:00
Nuno Maduro 135c8a0d46 docs: updates features 2024-07-18 20:43:11 +01:00
Nuno Maduro 1cdd7d6744 chore: updates dependencies 2024-07-18 20:42:52 +01:00
Nuno Maduro fca0c3a10c chore: bumps dependencies 2024-07-16 23:52:00 +01:00
Nuno Maduro 0331a87be1 feat: adds toHaveFileSystemPermissions expectation 2024-07-14 23:16:04 +01:00
Nuno Maduro ef120125e0 release: 2.34.9 2024-07-11 09:36:26 +01:00
Nuno Maduro 8a9a416133 chore: bumps dependencies 2024-07-11 09:35:43 +01:00
Nuno Maduro 4783334f15 chore: style 2024-07-11 09:35:38 +01:00
Nuno Maduro d3be6b72dd Merge pull request #1189 from MrPunyapal/fix/breaking-in-windows
feat: update filename check in Configuration class
2024-07-09 15:58:14 +01:00
Punyapal Shah 7d3118db65 feat: update filename check in Configuration class 2024-07-09 20:24:28 +05:30
Nuno Maduro eac7abebcb Merge pull request #1184 from edjw/patch-1
Include ds from Laradumps in ArchPresets
2024-07-07 22:47:49 +01:00
Nuno Maduro 6896dd486a fix: laravel preset 2024-07-07 22:46:15 +01:00
Nuno Maduro 1e5b399603 feat: adjusts default conf 2024-07-06 19:30:05 +01:00
Nuno Maduro ccdf43726d fix: pest() 2024-07-06 18:25:43 +01:00
Nuno Maduro 67dbce2d42 feat: more presets rules 2024-07-06 16:45:58 +01:00
Nuno Maduro ee32f25485 feat: pr and issue 2024-07-04 00:53:58 +01:00
Nuno Maduro 09ca7a1fd5 chore: features 2024-07-03 22:18:11 +01:00
Nuno Maduro dade84e6b6 fix: handle arguments 2024-07-03 22:17:47 +01:00
Nuno Maduro 1c4bc8b1dc fix: before each globally 2024-07-03 22:15:11 +01:00
Ed Johnson-Williams 0d2f3eb60e Include ds from Laradumps in ArchPresets 2024-06-28 22:33:15 +01:00
Nuno Maduro 29787d1ff1 Update Features.md 2024-06-27 11:32:55 +01:00
Nuno Maduro 474b9b7e17 chore: adjusts tests 2024-06-27 01:41:34 +01:00
Nuno Maduro 5c3bf469d5 feat: note() 2024-06-27 01:26:54 +01:00
Nuno Maduro d9252e85d6 chore: optimize preset 2024-06-26 22:32:58 +01:00
Nuno Maduro 0289466ce8 Allows other folders to be queuable 2024-06-25 23:06:25 +01:00
Nuno Maduro 57ef989df8 feat(presets): improve laravel preset 2024-06-25 22:02:23 +01:00
Nuno Maduro 9d02b649e2 feat: adds toUseTraits expectation 2024-06-25 21:56:08 +01:00
Nuno Maduro 00643312b7 chore: updates snapshots 2024-06-25 21:13:05 +01:00
Nuno Maduro eac6585a2e refactor: logger 2024-06-25 21:09:10 +01:00
Nuno Maduro 04c39bae2e Adjust style 2024-06-25 21:02:52 +01:00
Nuno Maduro c65755725d feat: improves type hinting with @param-closure-this 2024-06-15 15:37:35 +01:00
Nuno Maduro ec58040f6e feat: improves type hinting 2024-06-15 15:24:03 +01:00
Nuno Maduro 3fa73e40cc Bumps dependencies 2024-06-15 15:23:05 +01:00
Nuno Maduro c07513c6a0 chore: fixes tests 2024-06-11 23:11:38 +01:00
Nuno Maduro 85d91d5652 Merge pull request #1170 from pestphp/feat/presets
[3.x] Arch Presets
2024-06-11 22:54:12 +01:00
Nuno Maduro 02bae3b649 Merge branch '3.x' into feat/presets 2024-06-11 22:54:05 +01:00
Nuno Maduro 3ba2b68afc Merge pull request #1178 from MrPunyapal/feat/some-functions-to-avoid
Feat: some functions to avoid
2024-06-11 22:03:29 +01:00
Punyapal Shah ed3ec79aab pint 2024-06-11 21:26:40 +05:30
Punyapal Shah 894dca83f7 chore: update Arch.php to ignore 'assert' function in presets 2024-06-11 21:24:57 +05:30
Punyapal Shah b873b89b62 Restrict additional dangerous functions in Security.php 2024-06-11 21:20:32 +05:30
Punyapal Shah 1bee283d15 Update Base.php to include 'ereg' and 'eregi' in the list of restricted functions 2024-06-11 21:09:15 +05:30
Punyapal Shah 7b4dd410f6 chore: update Arch.php to ignore additional functions in presets 2024-06-11 21:08:02 +05:30
Punyapal Shah 4396ee2e03 feat(presets): update Security.php to restrict additional dangerous functions 2024-06-11 21:02:19 +05:30
Punyapal Shah e4550c8d51 Update Base.php to include 'global' in the list of restricted functions 2024-06-11 20:58:52 +05:30
Punyapal Shah a25cfb435c Update Base.php to include 'mysql_*' in the list of restricted functions 2024-06-11 20:56:49 +05:30
Nuno Maduro fe4fe12df1 chore: updates snapshots 2024-06-10 23:10:20 +01:00
Nuno Maduro 3bcc99a372 Merge branch '2.x' into 3.x 2024-06-10 23:04:34 +01:00
Nuno Maduro e8f122bf47 release: 2.34.8 2024-06-10 23:02:16 +01:00
Nuno Maduro 9fc607a2b8 Fixes wrong stub 2024-06-10 22:52:21 +01:00
Nuno Maduro 3ad788dddb feat(presets): adjusts laravel preset 2024-06-10 22:38:40 +01:00
Nuno Maduro 2108d18be5 Merge pull request #1176 from MrPunyapal/feat/laravel-preset
Feat: laravel preset
2024-06-10 22:36:21 +01:00
Nuno Maduro aa4a5fcd15 Merge pull request #1175 from Shotman/preset-buggregator-trap
Add trap to list
2024-06-10 22:34:02 +01:00
Punyapal Shah 1688888f15 feat(presets): update Laravel preset to include expectation for Laravel service provider suffix 2024-06-10 21:49:03 +05:30
Punyapal Shah 40539ca720 feat(presets): update Laravel preset to include expectation for Laravel service provider suffix 2024-06-10 21:48:25 +05:30
Punyapal Shah 7144d6dfbd feat(presets): add expectation for Laravel notification class 2024-06-10 21:47:12 +05:30
Punyapal Shah 7240250a15 feat(presets): add expectation for Laravel job handle method 2024-06-10 21:47:05 +05:30
Punyapal Shah 508e42a2ff feat(presets): update Laravel preset to include expectation for Laravel request suffix 2024-06-10 21:45:17 +05:30
Punyapal Shah d8156fee53 feat(presets): add expectation for Laravel middleware handle method 2024-06-10 21:44:49 +05:30
Punyapal Shah abc245bf85 feat(presets): add expectation for Laravel command handle method 2024-06-10 21:40:16 +05:30
Punyapal Shah 65dacd5647 feat(presets): add expectation for Laravel mail class 2024-06-10 21:38:20 +05:30
Punyapal Shah 917f7a64a0 feat(presets): add expectation for Laravel exception suffix 2024-06-10 21:36:59 +05:30
Punyapal Shah e8b09d6f8c feat(presets): add expectation for Laravel command suffix 2024-06-10 21:35:42 +05:30
Shotman 0c4e6de823 Add trap to list
Adding trap function from https://github.com/buggregator/trap docs:  https://docs.buggregator.dev/trap/getting-started.html
2024-06-10 18:03:46 +02:00
Punyapal Shah 52282cc590 feat(presets): add expectation for Laravel request suffix 2024-06-10 21:29:12 +05:30
Punyapal Shah a46142d8c7 feat(presets): update Laravel preset to exclude models with suffix 'Model' 2024-06-10 21:28:08 +05:30
Punyapal Shah 241dcf8f34 feat(presets): add expectation for Laravel controller suffix 2024-06-10 21:25:51 +05:30
Nuno Maduro 927cee609e Update src/ArchPresets/Base.php
Co-authored-by: Joshua Gigg <giggsey@gmail.com>
2024-06-10 15:19:55 +01:00
Nuno Maduro 98e4ebb8fd feat(presets): fixes return type 2024-06-10 11:40:25 +01:00
Nuno Maduro c173e3e86b feat(presets): allows usage of laravel preset 2024-06-10 11:39:31 +01:00
Nuno Maduro c73655f4f9 feat(presets): adds xdebug_* functions 2024-06-10 11:22:51 +01:00
Nuno Maduro 4ac1c6efc6 feat(presets): adds goto 2024-06-10 11:16:46 +01:00
Nuno Maduro 2e5a308b0d Merge pull request #1174 from ClaraLeigh/feat/presets
feature(presets): Add security preset
2024-06-10 11:13:50 +01:00
Clara 7b8e4aec08 feature(presets): Add security preset
Looks for functions often seen as insecure
2024-06-10 11:44:45 +10:00
Nuno Maduro 13fb66f15c feat(presets): ignores ddd 2024-06-10 02:22:41 +01:00
Nuno Maduro dd1bd92910 feat(presets): more rules 2024-06-10 01:15:21 +01:00
Nuno Maduro d665b53b22 Merge pull request #1173 from faissaloux/add-echo-and-print-to-base
Add `echo` and `print` to base preset
2024-06-10 00:49:15 +01:00
faissaloux c54b7e400e echo and print not to be used in base preset 2024-06-10 00:00:57 +01:00
Nuno Maduro c1e1fff0d0 feat(presets): ignores ddd 2024-06-09 23:47:53 +01:00
Nuno Maduro 2e4a8329a6 feat(presets): keeps improving base presets 2024-06-09 22:46:21 +01:00
Nuno Maduro 878988a02d feat(presets): ignores eval for now 2024-06-09 22:38:30 +01:00
Nuno Maduro ceb7244b43 feat(presets): refactors code 2024-06-09 22:23:10 +01:00
Nuno Maduro 84256aa8b9 Merge pull request #1172 from MrPunyapal/feat/add-ini_set-into-base
feat: add 'ini_set' to list of expected functions in Base.php
2024-06-09 15:03:33 +01:00
Punyapal Shah d6b59e4e96 feat: add 'ini_set' to list of expected functions in Base.php 2024-06-09 19:32:49 +05:30
Nuno Maduro 087d09120a Merge pull request #1171 from MrPunyapal/feat/add-eval-in-base
feat: add 'eval' to list of expected functions in Base.php
2024-06-09 14:47:25 +01:00
Punyapal Shah cc41a7f81d feat: add 'eval' to list of expected functions in Base.php 2024-06-09 19:16:29 +05:30
Nuno Maduro bd16769b93 fixes tests 2024-06-09 00:05:18 +01:00
Nuno Maduro 60b1e63c23 feat: initial work on presets 2024-06-08 20:54:46 +01:00
Nuno Maduro c7bcb6eb7b chore: updates snapshots 2024-06-08 14:01:41 +01:00
Nuno Maduro d25ec50384 chore: bumps dependencies 2024-06-08 14:01:31 +01:00
Nuno Maduro 9e27813897 Adjusts configuration 2024-06-08 13:12:17 +01:00
Nuno Maduro b33af71036 Merge pull request #1157 from ExeQue/2.x
[2.x] Added `toBeList` expectation
2024-05-27 13:08:05 +01:00
Morten Harders 3c6c89a6ad Added test to toBeList 2024-05-21 08:15:32 +02:00
Morten Harders 55f6b5696e Added toBeList expectation 2024-05-21 08:13:20 +02:00
Nuno Maduro ba914fa2fb fix: add more extends and traits 2024-05-14 20:28:49 +01:00
Nuno Maduro c919bb5bc4 feat: adds pest function 2024-05-14 01:58:44 +01:00
Nuno Maduro 8169382362 feat: adds after 2024-05-08 01:24:30 +01:00
Nuno Maduro 04b099e87c docs: adds backlog 2024-05-08 01:24:16 +01:00
Nuno Maduro fecdb7f572 chore: updates deps 2024-05-08 01:24:08 +01:00
Nuno Maduro b611d0d444 wording 2024-04-30 20:48:49 +01:00
Nuno Maduro ac7199c96d wip 2024-04-30 20:48:27 +01:00
Nuno Maduro 7756457dc4 Merge pull request #1146 from JHWelch/interpolated-dataset-names
Interpolated dataset names
2024-04-30 19:16:54 +01:00
Jordan Welch 10da81eee4 Check on non named tests 2024-04-29 18:10:35 -05:00
Jordan Welch 8bbee3c1e5 Add working interpolated dataset name 2024-04-29 18:01:09 -05:00
Nuno Maduro 16125df77b chore: fixes test suite 2024-04-28 12:30:56 +01:00
Nuno Maduro 80530cb1e0 wip: runs integration tests undert the v flag 2024-04-28 12:18:11 +01:00
Nuno Maduro 2070538fd3 chore: fixes test suite 2024-04-28 12:02:42 +01:00
Nuno Maduro a2cb78710d Fixes isset 2024-04-28 11:16:37 +01:00
Nuno Maduro 335bfdb79d fix: test suite 2024-04-28 11:02:24 +01:00
Nuno Maduro cfa00da885 Fixes test suite 2024-04-27 11:36:22 +01:00
Nuno Maduro f49d1e0e18 Merge branch '2.x' into 3.x 2024-04-26 23:14:53 +01:00
Nuno Maduro 303f4c0113 Adds sponsor 2024-04-19 20:47:46 +01:00
Nuno Maduro 35a1fcd0cf chore: updates readme 2024-04-08 12:28:43 +01:00
Tom Harper adb2fb51df Always call parent teardown even if an exception is thrown 2024-04-08 10:03:55 +01:00
Nuno Maduro a7a3e4240e release: v2.34.7 2024-04-05 08:44:17 +01:00
Nuno Maduro e4af33867b chore: bumps dependencies 2024-04-05 08:44:07 +01:00
Nuno Maduro 0c51b159a7 chore: fixes test suite 2024-04-01 13:24:30 +01:00
Nuno Maduro c6984323c3 chore: fixes test suite 2024-04-01 13:15:33 +01:00
Nuno Maduro 831d9bf49a tests 2024-03-29 23:12:10 +00:00
Nuno Maduro 12f6aa604c chore: versions 2024-03-29 23:12:00 +00:00
Nuno Maduro 265f0c7da9 Merge branch '2.x' into 3.x 2024-03-29 23:09:19 +00:00
Nuno Maduro 680111fb1e release: v2.34.6 2024-03-28 11:36:46 +00:00
Nuno Maduro aa6ff95ea4 chore: bumps dependencies 2024-03-28 11:36:36 +00:00
Nuno Maduro 863a0cc837 release: v2.34.5 2024-03-22 08:44:19 +00:00
Nuno Maduro 126a84a63e chore: bumps dependencies 2024-03-22 08:44:13 +00:00
Peter Fox 0ccbe5c8f0 Remove Laravel serialisable closure 2024-03-17 17:23:17 +00:00
Peter Fox a4f8ae1a12 Handles tests where a static closure is provided 2024-03-17 16:48:43 +00:00
Peter Fox 6094682158 Add static closure check 2024-03-17 12:04:38 +00:00
Nuno Maduro d519e40b95 chore: adjusts workflow 2024-03-15 21:14:22 +00:00
Nuno Maduro 6a1161ead8 release: v2.34.4 2024-03-14 19:44:18 +00:00
Nuno Maduro a1b3547dd6 chore: fixes paratest version 2024-03-14 19:42:03 +00:00
Nuno Maduro b9e3146a47 release: v2.34.3 2024-03-14 19:40:23 +00:00
Nuno Maduro ce1607cba9 chore: bumps phpunit 2024-03-14 19:40:16 +00:00
Nuno Maduro ac07bc1770 chore: override changes 2024-03-14 19:40:03 +00:00
Nuno Maduro 521a41dd10 fix: no duplicate --no-output 2024-03-14 19:39:45 +00:00
Nuno Maduro 1b68b340e8 chore: fixes static analysis 2024-03-14 19:39:31 +00:00
Nuno Maduro 853f6efce6 release: v2.34.2 2024-03-11 18:05:47 +00:00
Nuno Maduro 62a9a78ee2 chore: bumps dependencies 2024-03-11 18:05:37 +00:00
Nuno Maduro 78d9fd31d0 release: v2.34.1 2024-02-28 15:15:55 +00:00
faissaloux 2c3234fb3d fix bool type 2024-02-21 17:09:16 +01:00
faissaloux 1b64fef7ba fix toUseStrictTypes 2024-02-21 16:58:40 +01:00
Ariful Alam a136231503 fix: modify Result::exitCode logic to address warning handling with --fail-on-warning 2024-02-20 18:24:37 +06:00
Nuno Maduro 602b696348 release: v2.34.0 2024-02-17 10:06:53 +00:00
Nuno Maduro 5b0f88c227 release: v2.33.6 2024-02-12 08:55:32 +00:00
Nuno Maduro f31a2c3220 chore: fixes paratest 2024-02-12 08:53:04 +00:00
Nuno Maduro e8fa98c810 release: 2.33.5 2024-02-12 08:44:52 +00:00
Nuno Maduro 07e314fbf5 chore: bumps dependencies 2024-02-12 08:44:40 +00:00
Nuno Maduro 4baf27911e release: 2.33.4 2024-02-02 16:54:54 +00:00
Nuno Maduro 12e48a14d1 chore: fixes deps 2024-02-02 16:53:58 +00:00
Nuno Maduro 1bc0f79508 release: 2.33.3 2024-02-02 16:51:42 +00:00
Nuno Maduro cb0f256791 release: 2.33.2 2024-02-02 16:50:40 +00:00
Nuno Maduro 7b9bae0415 fix: usage of named arguments 2024-02-01 13:45:06 +00:00
Nuno Maduro 3dffdf7cb8 Merge branch '2.x' into 3.x 2024-02-01 11:53:10 +00:00
Nuno Maduro 923970a117 chore: bumps versioning 2024-02-01 11:51:11 +00:00
Nuno Maduro b3db7dfd4c chore: fixes type checking 2024-02-01 11:45:19 +00:00
Nuno Maduro b303f9f818 Merge pull request #1082 from nhaynes/fix-ci-flag
fix: updates Only plugin to check for CI environment
2024-01-30 14:17:03 +00:00
Nick Haynes d29997d5b0 fix: updates Only plugin to check for CI environment 2024-01-29 19:03:06 -06:00
Nuno Maduro 13f340a742 feat: improves badge coloring 2024-01-29 23:00:30 +00:00
Nuno Maduro eeade88ad2 Fixes kernel throwing all kind of errors 2024-01-29 12:50:00 +00:00
Nuno Maduro 06280ef75d chore: updates snaphosts 2024-01-29 11:54:23 +00:00
Nuno Maduro aa46f73888 Merge pull request #1081 from nuernbergerA/track-vendor-changes
[2.x] Track vendor changes
2024-01-29 11:39:34 +00:00
Adrian Nürnberger 3660865e5e update snapshot 2024-01-29 08:58:07 +01:00
Adrian Nürnberger 13695d597b Merge branch '2.x' into track-vendor-changes 2024-01-29 08:56:41 +01:00
Nuno Maduro fab2de833f Merge pull request #1080 from nuernbergerA/test-junit
[2.x] Add test for junit implementation
2024-01-28 23:49:06 +00:00
Adrian Nürnberger 5b630bcdff possible implementation 2024-01-28 09:48:37 +01:00
Adrian Nürnberger e70edbfa38 normalize path for windows 2024-01-28 09:11:06 +01:00
Adrian Nürnberger b1558ddde5 update snapshot 2024-01-28 09:04:42 +01:00
Adrian Nürnberger 582529377b add test for junit output 2024-01-28 08:53:20 +01:00
Nuno Maduro 88714598b6 Merge pull request #1076 from pestphp/fixing-version
[2.x] Fixing Version `2.33` for New Release
2024-01-27 13:01:06 +00:00
AJ 5136267bbe fixing version for new release 2024-01-26 23:43:52 -03:00
Nuno Maduro 19e748f0d4 chore: adjusts snapshots 2024-01-26 01:58:03 +00:00
Nuno Maduro a53a9d03cf fix: exiting 2024-01-26 00:12:36 +00:00
Nuno Maduro edaa045283 feat: exists after kernel shutdown 2024-01-26 00:04:52 +00:00
Nuno Maduro c5ce355f3c feat: improves fatal exception handling 2024-01-25 21:47:31 +00:00
Nuno Maduro 62d8459627 Merge pull request #1075 from luismgsantos/fix/docker-build-image
fix: build failing to run
2024-01-25 17:56:50 +00:00
Nuno Maduro a5bf6a3fcb fix: --cache-directory being used on phpunit file 2024-01-25 17:56:24 +00:00
Nuno Maduro 7a46514df8 fix: removes process-isolation from --help output 2024-01-25 17:32:02 +00:00
Nuno Maduro cb1735f4d8 fix: removes process-isolation from --help output 2024-01-25 17:27:03 +00:00
Nuno Maduro 607a4906ac Merge pull request #1006 from JonPurvis/to-be-backed-enum-expectation
[2.x] Add `toBeStringBackedEnum()` and `toBeIntBackedEnum()` Architecture Expectations
2024-01-25 17:09:23 +00:00
Luís Santos 317ea0356e fix: build failing to run 2024-01-25 18:06:35 +01:00
Nuno Maduro 1153531104 Merge pull request #1055 from mapon-com/feature/string-comparison-expectations
[2.x] Allow string type in greaterThan/lessThan expectations
2024-01-25 16:48:27 +00:00
Nuno Maduro cfb724cd77 Merge pull request #1060 from calebdw/bugfix-code_coverage
[2.x] fix: warn if no code coverage driver
2024-01-25 16:42:03 +00:00
Nuno Maduro 0060b6f955 Merge pull request #1069 from davybaccaert/improve_coverage_message_on_failing_minimum_requirements
[2.x] Improve coverage output message on failing minimum requirements
2024-01-25 16:39:30 +00:00
Nuno Maduro 95cd550524 fix: pipes not allowing to modify original value 2024-01-25 16:10:16 +00:00
Nuno Maduro 815ae3c644 Merge pull request #975 from Katalam/repeat
[2.x] Sharing `repeat` iteration as `dataset` variable
2024-01-25 15:01:34 +00:00
Nuno Maduro 887bed3d45 fix: adjusts backtrace for pest's internal test suite 2024-01-25 15:00:04 +00:00
Nuno Maduro 79da02c500 Merge pull request #972 from Carnicero90/bugfix-backtrace-naming-conflicts
[2.x] Fixing Backtrace not found error if project dirname endswith pest
2024-01-25 14:54:58 +00:00
Nuno Maduro 0aecd5d5d7 Merge pull request #974 from erikgaal/expect-to-contain-equals
[2.x] Add `toContainEquals` expectation
2024-01-25 14:38:55 +00:00
Nuno Maduro e95c4ee636 feat(toContainEqual): adds method name 2024-01-25 14:38:44 +00:00
Nuno Maduro 2e7fec6be5 Merge pull request #961 from bastien-phi/allow_multiple_hook_per_directory
[2.x] Allow define multiple hooks per directory
2024-01-25 14:31:29 +00:00
Nuno Maduro 4be7082de5 chore: updates snapshots 2024-01-25 14:31:17 +00:00
Bastien Philippe fb90f778b9 Update snapshots 2024-01-25 14:28:37 +00:00
Bastien Philippe 9d58e1a77e Add ability to define multiple hooks for the same directory in Pest.php 2024-01-25 14:23:41 +00:00
Nuno Maduro 9c077ed352 refacto: moves function to being used on internal test suite only 2024-01-25 14:13:18 +00:00
Nuno Maduro 2562d36518 feat: clarfies that high order testing does not support bound datasets 2024-01-25 14:12:01 +00:00
Nuno Maduro 1d2fe2de2d fix: doNotThrowsExceptions being marked as incomplete 2024-01-25 14:12:01 +00:00
Nuno Maduro 2d82ee2837 chore: fixes types 2024-01-25 14:12:01 +00:00
Nuno Maduro 1eee9df679 Merge pull request #981 from salehhashemi1992/refactor/remove-ansi-sequences
[2.x] Refactor: Extract ANSI Escape Sequence Removal to a Function
2024-01-25 14:11:51 +00:00
Nuno Maduro 8c57cc1731 fix: --watch plugin access to original arguments 2024-01-25 12:33:20 +00:00
Nuno Maduro 4febd8a11b Merge pull request #1073 from nuernbergerA/fix-junit-parallel
Fix junit parallel
2024-01-25 10:17:36 +00:00
Adrian Nürnberger 880b003bee apply cs 2024-01-24 21:50:52 +01:00
Adrian Nürnberger e0f9d0bccf just override the phpunit file 2024-01-24 21:33:40 +01:00
Adrian Nürnberger d4853feecd drop own implementation 2024-01-24 21:33:17 +01:00
Adrian Nürnberger 86e812284d remove plugin to ensure argument reaches paratest 2024-01-24 21:32:49 +01:00
Nuno Maduro 4e31973040 Merge branch '2.x' into 3.x 2024-01-23 18:15:06 +00:00
Nuno Maduro f75063c420 release: 2.32.2 2024-01-23 18:12:07 +00:00
Nuno Maduro 6a48e9d44b Merge branch '2.x' into 3.x 2024-01-23 18:06:23 +00:00
Nuno Maduro 1f8e6e4e9f fix: helper access 2024-01-23 17:40:37 +00:00
Nuno Maduro bb593846e5 release: 2.32.1 2024-01-23 17:04:48 +00:00
Davy Baccaert 108d181a05 Improve coverage output message on failing minimum requirements 2024-01-20 15:29:35 +01:00
Nuno Maduro ac5d6c1f67 chore: fixes constrains no workflow 2024-01-20 13:48:00 +00:00
Nuno Maduro 5aa3b91d56 chore: fixes windows builds 2024-01-20 13:36:31 +00:00
Nuno Maduro 9a01504b76 chore: fixes workflow 2024-01-20 13:32:21 +00:00
Nuno Maduro 0ab636e436 chore: fixes workflow 2024-01-20 13:28:43 +00:00
Nuno Maduro b9d2be87a2 fix: missing things on junit 2024-01-20 13:21:57 +00:00
Nuno Maduro fef02594db release: 2.32.0 2024-01-20 11:44:11 +00:00
Nuno Maduro e135e2671f style 2024-01-20 11:44:11 +00:00
Nuno Maduro 6d74965727 chore: bump dependencies 2024-01-20 11:44:11 +00:00
Nuno Maduro 146e141b2a Merge pull request #887 from nuernbergerA/fix-junit-output
[2.x] Junit support
2024-01-20 11:43:20 +00:00
Nuno Maduro 6fed7545c0 Merge pull request #990 from rudashi/patch-1
[2.x] Fix typo in `toHaveProperties` PHPDoc block
2024-01-13 01:44:16 +00:00
Caleb White be407ac904 fix: warn if no code coverage driver 2024-01-11 10:20:35 -06:00
Nuno Maduro 9ce52ee7ce chore: fixes snaphots 2024-01-11 16:03:48 +00:00
Nuno Maduro 3ff41bcb68 Merge branch '2.x' into 3.x 2024-01-11 16:03:33 +00:00
Nuno Maduro 5332858782 chore: fixes snapshots 2024-01-11 15:46:50 +00:00
Nuno Maduro 2b094b4188 Merge branch '2.x' into 3.x 2024-01-11 15:37:31 +00:00
Nuno Maduro 3457841a9b release: v2.31.0 2024-01-11 15:33:20 +00:00
Nuno Maduro 5258e569c1 feat: adds skipOnPHP 2024-01-11 15:33:12 +00:00
Nuno Maduro abb416c2ff chore: bumps dependencies 2024-01-11 15:32:44 +00:00
Nuno Maduro dd4d5bbd4e chore: adjusts snapshots 2024-01-11 11:33:02 +00:00
Nuno Maduro ab64912c70 chore: adjusts coding style 2024-01-11 11:30:11 +00:00
Nuno Maduro 1506d8bb27 chore: uses PHP 8.2 for static testing 2024-01-11 11:28:45 +00:00
Nuno Maduro 5aa13b8e97 chore: adjusts coding style 2024-01-11 11:26:48 +00:00
Nuno Maduro b143ed7aac chore: uses Symfony Console 7.0.2 2024-01-11 11:17:21 +00:00
Nuno Maduro 26dd5f298f chore: adjusts tests 2024-01-11 10:51:45 +00:00
Nuno Maduro d939ee938e chore: bumps dependencies 2024-01-11 10:36:24 +00:00
Nuno Maduro 515de3972f Merge branch '2.x' into 3.x 2024-01-10 11:51:11 +00:00
Nuno Maduro bf573b3cac chore: makes tests pass 2024-01-08 13:16:53 +00:00
Nuno Maduro 53dc9ffa06 feat: always use attributes instead of annotations 2024-01-05 18:00:14 +00:00
Nuno Maduro 04d2fa5ce8 feat: moves covers nothing to attribute 2024-01-05 14:37:33 +00:00
Nuno Maduro 7764a7a162 chore: bumps dependencies 2024-01-05 14:37:24 +00:00
Nuno Maduro 727a427837 feat: adjust overrides 2024-01-05 14:37:13 +00:00
Pauls Nartišs b1c59ec2e6 feat: allow string type in gt/lt expectations 2024-01-05 16:21:02 +02:00
Nuno Maduro f69a3cf832 chore: bumps dependencies 2024-01-05 11:09:32 +00:00
Nuno Maduro ed0bf1786f chore: fixes conflict 2024-01-05 10:24:49 +00:00
Nuno Maduro 2d1d8a81e1 Keeps working on dependencies 2024-01-05 09:50:45 +00:00
Nuno Maduro d515cf965e chore: bumps dependencies 2024-01-04 18:41:22 +00:00
Nuno Maduro dc1e4f040d docs: adds sponsor 2024-01-04 18:26:20 +00:00
Nuno Maduro 5e1e701ce5 Merge pull request #1051 from krencl/fix-cache-directory-config-override
Fix cache directory config override
2024-01-02 14:33:48 +00:00
Marek Mencl f004591c5a fix: checking existing argument with equal sign 2024-01-02 15:03:46 +01:00
Marek Mencl 86a96dd157 fix: overriding cli argument --cache-directory 2024-01-02 15:01:13 +01:00
Nuno Maduro 97dc32f9d2 release: v2.30.0 2023-12-28 10:36:40 +00:00
Nuno Maduro a3ab065343 chore: coding style 2023-12-28 10:36:30 +00:00
Nuno Maduro c390721ac3 chore: update snapshots 2023-12-28 10:34:22 +00:00
Nuno Maduro f83d758d4b feat: adds fails 2023-12-28 10:31:39 +00:00
Nuno Maduro e00aba539a release: v2.29.1 2023-12-27 15:27:07 +00:00
Nuno Maduro 7799500d06 release: v2.29.0 2023-12-27 11:12:01 +00:00
Nuno Maduro c099991cd9 Merge pull request #1044 from nhrrs/fix-typo
Fix typo in `toBeClass`
2023-12-23 02:03:57 +00:00
Nick Harris e27d2e7394 Fix typo in toBeClass 2023-12-23 00:36:41 +00:00
Adrian Nürnberger 14fb992ef2 unify converter 2023-12-19 06:29:28 +01:00
Adrian Nürnberger 4550a344d3 overwrite phpunit junit logging with noop 2023-12-19 06:29:28 +01:00
Adrian Nürnberger 8efd25ef65 remove debug output 2023-12-19 06:29:28 +01:00
Adrian Nürnberger 117694f210 cleanup 2023-12-19 06:29:28 +01:00
Adrian Nürnberger e5dc6f0ae2 junit support 2023-12-19 06:29:28 +01:00
Nuno Maduro 8f738f5d49 Revert "Merge pull request #919 from WendellAdriel/feature/coverage-errors-only-flag-2"
This reverts commit 1e2ca40c5b, reversing
changes made to 4522cb5dcb.
2023-12-17 22:03:15 +00:00
Nuno Maduro 1e2ca40c5b Merge pull request #919 from WendellAdriel/feature/coverage-errors-only-flag-2
[2.x] Print only files below the min coverage
2023-12-17 21:56:14 +00:00
Nuno Maduro 4522cb5dcb Merge pull request #1014 from mjsafarali/chore/docker-file-optimization
[2.x] Dockerfile Optimization
2023-12-17 21:39:38 +00:00
Nuno Maduro 9ee4191020 release: v2.28.1 2023-12-15 11:42:34 +00:00
Nuno Maduro cc65009d0a chore: adds "phpunit/phpunit": "^10.5.3" support 2023-12-15 11:42:23 +00:00
Nuno Maduro 453133d382 chore: code style changes 2023-12-15 11:42:09 +00:00
Nuno Maduro dd0dddffd4 docs: updates sponsors 2023-12-11 12:05:58 +00:00
Nuno Maduro 9a8f6e6414 release: v2.28.0 2023-12-05 19:06:22 +00:00
Nuno Maduro 4ece95a040 tests: uses arch function 2023-12-05 19:06:11 +00:00
Nuno Maduro 0cc09380bc chore: bumps dependencies 2023-12-05 19:06:03 +00:00
Nuno Maduro 809fb855de release: v2.27.0 2023-12-04 11:11:35 +00:00
Nuno Maduro aa14f2e200 chore: uses specific symfony versions 2023-12-04 11:08:41 +00:00
Nuno Maduro e319bdb6d3 chore: fixes missing caret on workflow 2023-12-04 11:04:08 +00:00
Nuno Maduro fb7340b556 chore: fixes exclude key and add fail-fast 2023-12-04 11:02:41 +00:00
Nuno Maduro 0528fec083 chore: fixes duplicated key name on workflow 2023-12-04 10:59:58 +00:00
Nuno Maduro 1cbaaf6e12 chore: allows symfony 7 on composer 2023-12-04 10:55:34 +00:00
Nuno Maduro dc862f60b2 chore: adjusts workflow 2023-12-04 10:54:11 +00:00
Nuno Maduro ff04d54247 chore: adjusts workflow name 2023-12-04 10:40:29 +00:00
Nuno Maduro 330cf05177 chore: adjusts workflow 2023-12-04 10:38:37 +00:00
Nuno Maduro 42b5fa914c Fixes integration tests 2023-12-04 10:15:55 +00:00
Nuno Maduro 3b1026b7d7 chore: fixes workflow name 2023-12-04 10:14:51 +00:00
Nuno Maduro b6151e0d01 chore: tests against Symonfy 7 2023-12-04 10:10:36 +00:00
Nuno Maduro d6db2c13c1 Merge pull request #1025 from xiCO2k/fix/allow-todo-argument
[2.x] Allow `--todo` argument.
2023-11-30 10:47:10 +00:00
Francisco Madeira 07b6ff6c04 Update bin/pest
Co-authored-by: Owen Voke <development@voke.dev>
2023-11-30 07:49:24 +00:00
Francisco Madeira ac5da9e3f7 feat: Allow --todo argument. 2023-11-30 00:32:23 +00:00
Nuno Maduro 90fb8c602c release: v2.26.0 2023-11-29 09:09:09 +00:00
Nuno Maduro 3974a65a18 Merge pull request #1017 from markhuot/patch-2
[2.x] Add `toSnapshot` early return
2023-11-29 08:50:28 +00:00
markhuot 2a54b5819d #1017 adds early return toSnapshot test 2023-11-28 20:59:45 -05:00
Borys Żmuda 8be46b57a0 Update toHaveProperties() $names param 2023-11-24 09:16:13 +01:00
Nuno Maduro 7177791f1e Merge pull request #1020 from allanmcarvalho/2.x
Update Expectation.php
2023-11-23 17:42:51 +00:00
Allan Mariucci Carvalho c743b10a87 Update Expectation.php
Removed @internal phpdoc
2023-11-23 13:15:50 -03:00
Nuno Maduro 83f8de17c8 release: v2.25.0 2023-11-22 07:17:30 +00:00
Mark Huot da20a62e49 Add toSnapshot() early return
Sometimes objects need native toString() and toArray() methods that are different from what you want to snapshot.

This adds an explicit toSnapshot() method that will be called first (when set) allowing for better snapshot values than the generic methods offer.
2023-11-21 22:56:21 -05:00
Nuno Maduro c8d3e1a9fa Merge pull request #1012 from nahime0/2.x
[2.x] Added onlyOn* methods to run the test only on a specific OS
2023-11-21 01:01:24 +11:00
Vincenzo Petrucci f7705fe1c1 feat: onlyOn* methods, removed private onlyOn, rely instead on skipOn* methods 2023-11-20 14:51:38 +01:00
mohammad safarali 4f35dbc607 chore: optimized version of the Dockerfile 2023-11-18 14:57:03 +03:30
JonPurvis 2e01776272 add to be backed enum expectation 2023-11-18 03:31:35 +00:00
Vincenzo Petrucci cf23dfa477 feat: onlyOn* methods now use the private onlyOn method 2023-11-17 16:16:48 +01:00
Vincenzo Petrucci ab4787c667 feat: added onlyOn* methods to run the test only on a specific OS 2023-11-17 15:03:28 +01:00
Nuno Maduro bd6b166a62 Merge pull request #1002 from faissaloux/remove-double-plus
Remove double plus
2023-11-08 10:01:57 +00:00
faissaloux 17340947b3 remove double plus 2023-11-08 10:52:33 +01:00
Nuno Maduro f235d84d95 release: v2.24.3 2023-11-08 09:47:14 +00:00
Nuno Maduro 3c0d780696 Merge pull request #1001 from faissaloux/fix-html-in-descriptions-or-datasets
Fix html in descriptions or datasets
2023-11-08 09:40:48 +00:00
faissaloux 16768fca9f update snapshots/paralell test 2023-11-07 17:46:00 +01:00
faissaloux 95ec0a82b2 fix html in tests descriptions and datasets 2023-11-07 17:35:42 +01:00
Erik Gaal 15cd7187e9 Update toContainEquals.php 2023-11-06 10:31:48 +01:00
Nuno Maduro 0a680dd06e release: v2.24.2 2023-11-01 19:10:11 -04:00
Nuno Maduro 152892cc38 chore: bumps paratest 2023-11-01 19:06:05 -04:00
Nuno Maduro 9aad417fb2 Merge pull request #996 from CalebDW/phpstan
Create PHPStan extension and add `HigherOrderTapProxy` to `universalObjectCratesClasses`
2023-10-30 20:49:55 -04:00
Caleb White b58e0cba66 Add Expectation to universalObjectCratesClasses 2023-10-30 14:48:23 -05:00
Caleb White 74864c60e1 Create phpstan extension 2023-10-30 11:55:26 -05:00
Nuno Maduro fd4f161edd release: v2.24.1 2023-10-26 11:02:35 -04:00
Nuno Maduro e0939e3e99 chore: adds phpunit 10.4.2 support 2023-10-26 11:02:26 -04:00
Borys Żmuda 2cbecd10e6 Fix typo in toHaveProperties() PHPDoc block 2023-10-23 11:23:53 +02:00
Nuno Maduro 2cdd5e3ba0 fix: infer generic type from expectation 2023-10-21 11:06:26 +01:00
Nuno Maduro 811ef27ee4 release: v2.24.0 2023-10-17 10:07:18 +01:00
Nuno Maduro 22a7fd0656 chore: adjusts snapshots 2023-10-17 10:07:08 +01:00
Nuno Maduro 698c276cbe chore: fixes style 2023-10-17 10:06:58 +01:00
Nuno Maduro 6340656ece chore: bumps dependencies 2023-10-17 10:06:48 +01:00
Nuno Maduro 2d5840f947 Merge pull request #933 from hungthai1401/throws_unless
[2.x] Add `throwsUnless`
2023-10-17 10:03:16 +01:00
Nuno Maduro b8bb3684a3 Merge pull request #983 from Muhammad-Sarfaraz/patch-1
Polishing Up "TestDox.php' PHPDoc Blocks for Clarity
2023-10-17 10:02:08 +01:00
Sarfaraz Muhammad Sajib b8cd563569 Update src/Factories/Annotations/TestDox.php
Co-authored-by: Owen Voke <development@voke.dev>
2023-10-16 16:23:09 +06:00
Sarfaraz Muhammad Sajib 9fb64599de Polishing Up "TestDox.php' PHPDoc Blocks for Clarity
Added the missing parenthesis and period for proper punctuation and formatted the doc block to meet PHPDocumentor standards.
2023-10-16 10:56:06 +06:00
Nuno Maduro 502f37d280 chore: updates links 2023-10-15 12:10:07 +01:00
Nuno Maduro 29cfa8ec35 chore: updates sponsors 2023-10-14 11:34:00 +01:00
salehhashemi1992 86c107ae5e Extract ANSI escape sequence to a function 2023-10-13 20:16:46 +03:30
Nuno Maduro a63cd2e4f5 Merge pull request #980 from salehhashemi1992/fix/lifecycle-hook-scope
Fix TestCycle hook scope
2023-10-13 15:27:37 +01:00
salehhashemi1992 7249b59e52 fix lifecycle hook scope 2023-10-13 17:51:02 +03:30
Nuno Maduro 5c94d9994e Merge pull request #979 from salehhashemi1992/ci/checkout-update
Update actions/checkout to v4
2023-10-13 00:36:11 +01:00
salehhashemi1992 bb0a5d8323 update checkout to v4 2023-10-12 21:32:46 +03:30
Nuno Maduro b126e8e6e4 release: v2.23.2 2023-10-10 16:40:34 +01:00
Nuno Maduro 677129d23d chore: uses paratest 7.3.0 2023-10-10 16:39:04 +01:00
Nuno Maduro cef5c36885 release: v2.23.1 2023-10-10 15:57:14 +01:00
Nuno Maduro a343ba4a29 chore: adds PHPUnit 10.4.1 support 2023-10-10 15:57:07 +01:00
Nuno Maduro 21b30b22a7 release: v2.23.0 2023-10-10 15:41:56 +01:00
Nuno Maduro 449c4b6c5e chore: adds collision v8 support 2023-10-10 15:37:25 +01:00
Nuno Maduro 6513ad6ced release: v2.22.1 2023-10-10 14:59:16 +01:00
Nuno Maduro 12421c846e chore: adds termwind v2 support 2023-10-10 14:55:43 +01:00
Nuno Maduro a312cecede release: v2.22.0 2023-10-10 08:45:41 +01:00
Nuno Maduro 4be97ed314 Merge pull request #977 from JonPurvis/to-be-url-expectation
[2.x] Adds `toBeUrl()` Expectation
2023-10-09 20:06:52 +01:00
JonPurvis 5101b9dce3 add to be url expectation 2023-10-09 20:02:11 +01:00
Katalam 67e452e9ed chore: add docs 2023-10-06 15:10:02 +02:00
Katalam ecff90da1c fix: add repeat iteration as the last argument when combined with dataset 2023-10-06 15:07:48 +02:00
Nuno Maduro 2ffafd445d release: v2.21.0 2023-10-06 13:33:39 +01:00
Nuno Maduro 6068ef6150 feat: adds support for PHPUnit 10.4 2023-10-06 13:33:31 +01:00
Katalam 3ee5c29a00 feat: add repeat iteration as function argument if no extra dataset is provided 2023-10-05 23:07:03 +02:00
Nuno Maduro 8c0b933fcd chore: bumps dependencies 2023-10-05 18:32:07 +01:00
Nuno Maduro 991e02649a chore: bumps paratest 2023-10-05 09:42:56 +01:00
Erik Gaal 79f5973e5a Add tests 2023-10-03 11:09:26 +02:00
Erik Gaal 37c40cb735 Add toContainEquals expectation 2023-10-03 10:55:57 +02:00
Filippo Montani 28ee2917f1 Fixing Backtrace not found error if project dirname endswith pest 2023-09-30 00:05:42 +02:00
Nuno Maduro a8b785f69e release: v2.20.0 2023-09-29 19:05:52 +01:00
Nuno Maduro 56610d886d Merge pull request #968 from JonPurvis/add-to-be-between-expectation
[2.x] Add `toBeBetween` expectation
2023-09-29 19:01:05 +01:00
JonPurvis be0d9e964b add toBeBetween() expectation 2023-09-22 01:55:06 +01:00
Nuno Maduro 6bc9da3fe1 chore: bumps collision 2023-09-19 11:48:16 +01:00
Nuno Maduro 6f54462070 fix: sync wrapper runner with paratest 2023-09-19 11:27:09 +01:00
Nuno Maduro 876629b744 release: v2.19.1 2023-09-19 11:01:29 +01:00
Nuno Maduro 5e74e5a19d release: v2.19.0 2023-09-19 10:48:34 +01:00
Nuno Maduro 0d114e21fd chore: updates snapshots 2023-09-19 10:48:23 +01:00
Nuno Maduro 95b65fe72b Merge pull request #962 from JonPurvis/construct-destruct-expectations
add toHaveConstructor() and toHaveDestructor() expectations
2023-09-18 11:37:46 +01:00
JonPurvis bc08f2cb55 fix style issues 2023-09-18 01:13:51 +01:00
JonPurvis 6c73a3d90b initial commit 2023-09-18 01:00:50 +01:00
Nuno Maduro c08f33638a chore: updates release 2023-09-13 23:16:44 +01:00
Nuno Maduro 6c93390c9c chore: removes changelog.md 2023-09-13 23:15:56 +01:00
Nuno Maduro b53e396aac release: v2.18.2 2023-09-13 23:14:31 +01:00
Nuno Maduro 8b327aa8b4 chore: adds phpunit 10.3.4 support 2023-09-13 23:14:22 +01:00
Nuno Maduro d0c6f9bc60 Merge pull request #957 from adevade/patch-1
Switch mixed indentation to spaces only in Laravel stub for `phpunit.xml.stub`
2023-09-12 16:15:39 +01:00
Andréas Lundgren b5e066939b Whoops 2023-09-12 09:04:30 +02:00
Andréas Lundgren 7892237408 Update Laravel phpunit.xml.stub indentation 2023-09-12 09:03:41 +02:00
Nuno Maduro 74df53c72b release: v2.18.1 2023-09-11 11:38:47 +01:00
Nuno Maduro ee26457705 Merge pull request #956 from Itemshopp/phpunit-xml-stub-update
[2.x] Update phpunit.xml stub file
2023-09-11 11:32:34 +01:00
MHO 09e6a0944a Removed self closing coverage tag from phpunit xml stub files 2023-09-11 11:03:25 +02:00
MHO bdee46043a Reformatted php unit xml tag attributes in both init stubs files 2023-09-10 13:37:26 +02:00
MHO 3e25168777 Corrected incorrect indentation in laravel phpunit xml slug 2023-09-10 13:00:55 +02:00
MHO 21b8507252 Updated Laravel init phpunit.xml stub file 2023-09-08 16:40:25 +02:00
MHO d8e283777e Updated phpunit.xml stub file 2023-09-08 15:39:44 +02:00
Nuno Maduro 2b0aa4b9c9 release: v2.18.0 2023-09-07 19:00:46 +01:00
Nuno Maduro 040eb8142d chore: phpunit 10.3.3 support 2023-09-07 19:00:26 +01:00
Nuno Maduro d1aeabc9da chore: style changes 2023-09-06 12:19:27 +01:00
Nuno Maduro e4ec2b3efa chore: updates snapshots 2023-09-06 11:58:48 +01:00
Nuno Maduro dedcc6b887 Merge pull request #950 from hungthai1401/wrong_comment
[2.x] Correct some  comment messages in `OppositeExpectation`
2023-09-06 11:55:17 +01:00
Nuno Maduro 2b0ed2bc45 Merge pull request #948 from hungthai1401/to_be_uuid
[2.x] Add `toBeUuid` expectation
2023-09-06 11:54:05 +01:00
Nuno Maduro 9c859ae7c4 Merge branch '2.x' into to_be_uuid 2023-09-06 11:53:58 +01:00
Nuno Maduro ae0a230046 chore: improves readability 2023-09-06 11:48:53 +01:00
Nuno Maduro 644fade478 Merge pull request #949 from pestphp/fix-depends-with-describe
[2.x] Fix the Usage of `depends` With `describe`
2023-09-06 11:47:34 +01:00
Thai Nguyen Hung c9e919dd40 fix: correct comment message in OppositeExpectation 2023-09-06 08:34:48 +07:00
Thai Nguyen Hung 42323e27b1 fix: correct method name 2023-09-06 08:21:42 +07:00
AJ Meireles 3927177b23 finishing the code 2023-09-05 20:36:18 -03:00
Thai Nguyen Hung 038fd80428 feat: toBeUUID expectation 2023-09-05 08:25:02 +07:00
Nuno Maduro cc6c5bf199 docs: updates changelog 2023-09-04 00:20:57 +01:00
Nuno Maduro b88d9e8ff2 tests: update snapshots 2023-09-03 23:24:52 +01:00
Nuno Maduro 0fc232bbc7 Merge pull request #934 from hungthai1401/to_have_attribute_expectation
[2.x] Add `toHaveAttribute` expectation
2023-09-03 23:18:47 +01:00
Nuno Maduro 7dcd42d113 chore: prepares release 2023-09-03 21:39:21 +01:00
Nuno Maduro e79ffc6bad tests: adjusts snapshots 2023-09-03 21:36:48 +01:00
Nuno Maduro 8ea425b266 Merge pull request #947 from ludoguenet/2.x
[2.x] Add `toHaveMethod` arch expectation
2023-09-03 21:32:01 +01:00
Nuno Maduro 3a0f6a1d09 chore: prepares release 2023-09-03 13:37:26 +01:00
Ludovic Guénet b9b90295fa Update Expectation.php
Typo
2023-09-01 18:39:37 +02:00
ludoguenet 9dabecacbf Add toHaveMethod arch expectation 2023-09-01 18:32:40 +02:00
Nuno Maduro 04fa6b6372 Merge pull request #943 from fabio-ivona/datasets-in-pest-file
fix directory separator for windows
2023-08-29 10:36:58 +01:00
Nuno Maduro a0d2856f51 docs: update changelog 2023-08-29 10:36:06 +01:00
Nuno Maduro 55b9266648 release: v2.16.1 2023-08-29 10:30:36 +01:00
Nuno Maduro 4313a1ef20 chore: bump dependencies 2023-08-29 10:30:28 +01:00
Nuno Maduro 005ef03845 chore: bumps dependencies 2023-08-29 10:17:07 +01:00
Fabio Ivona bbac28c9f4 fix directory separator for windows 2023-08-29 11:14:23 +02:00
Nuno Maduro eb56483ba2 Merge pull request #942 from fabio-ivona/datasets-in-pest-file
[fix] missing datasets when defined in Pest.php file
2023-08-29 09:54:09 +01:00
Fabio Ivona 5d6b717c9a fix missing datasets when defined in Pest.php file 2023-08-29 10:49:17 +02:00
Thai Nguyen Hung e888f3613b refactor: change falsy to false 2023-08-24 16:40:30 +07:00
Thai Nguyen Hung e69899559d refactor: generic attribute 2023-08-24 15:23:13 +07:00
Thai Nguyen Hung e6fe968d44 fix: pint 2023-08-24 14:45:11 +07:00
Thai Nguyen Hung 678898efe7 feat: toHaveAttribute expectation 2023-08-24 14:26:54 +07:00
Thai Nguyen Hung 6c3d8829ce feat: throwsUnless method 2023-08-24 09:28:47 +07:00
Nuno Maduro 14859a4c89 Merge pull request #930 from pestphp/feature/same-size-arg
[2.x] chore: resolve `toHaveSameSize` parameter
2023-08-23 12:01:34 +01:00
Owen Voke 8a44d3f136 chore: resolve toHaveSameSize parameter 2023-08-23 11:28:41 +01:00
Nuno Maduro be71d6918d chore: bump dependencies 2023-08-23 10:35:06 +01:00
Nuno Maduro afb3dd459a Merge pull request #924 from hungthai1401/to_have_same_size_expectation
[2.x] Add `toHaveSameSize` expectation
2023-08-23 10:14:17 +01:00
Thai Nguyen Hung b6e3ffafa7 fix: phpstan 2023-08-23 08:14:27 +07:00
Nuno Maduro 6c95f3d8cf Merge pull request #923 from hungthai1401/inconsistent_type_have_count_exception
[2.x] Inconsistent type have count exception
2023-08-22 10:37:59 +01:00
Thai Nguyen Hung 2192373bec test: toHaveSameSize 2023-08-22 11:10:38 +07:00
Thai Nguyen Hung dfcdaa3f8e feat: toHaveSameSize expectation 2023-08-22 11:10:25 +07:00
Thai Nguyen Hung 79bc9e677f test: toHaveCount with invalid type 2023-08-22 10:36:10 +07:00
Thai Nguyen Hung 60b615ea6a fix: inconsistent type in InvalidExpectationValue exception at toHaveCount expectation 2023-08-22 10:35:07 +07:00
Nuno Maduro 8787481e40 docs: updates changelog 2023-08-21 09:53:42 +01:00
Nuno Maduro c24406259f docs: updates changelog 2023-08-21 09:51:12 +01:00
Nuno Maduro cbd6a65057 release: v2.16.0 2023-08-21 09:42:07 +01:00
Nuno Maduro 175004baf3 chore: adds testing on native functions 2023-08-21 09:40:04 +01:00
Nuno Maduro 6d9c0483a6 chore: improves type checking 2023-08-21 09:39:55 +01:00
Nuno Maduro 2dc413cba0 tests: update snapshots 2023-08-19 10:42:42 +01:00
Nuno Maduro 206548af2b Merge pull request #895 from cerbero90/feature/traversable-sequence
[2.x] Add support for nested traversable in sequence
2023-08-19 09:38:46 +00:00
Nuno Maduro af6de422e9 Merge pull request #921 from leMaur/feat/string-case-expectations
feat: add string case expectations
2023-08-19 09:38:31 +00:00
Maurizio 1c7b254395 Merge branch '2.x' into feat/string-case-expectations 2023-08-19 10:28:48 +02:00
Maurizio de1c721cd9 chore: improve error messages 2023-08-19 10:27:21 +02:00
Maurizio f8dd286213 chore: skip array list 2023-08-19 10:27:02 +02:00
Andrea Marco Sartori e11337df2d Merge branch '2.x' into feature/traversable-sequence 2023-08-19 00:34:30 +02:00
Nuno Maduro 2f90d4ccd7 tests: update snapshots 2023-08-18 12:16:15 +01:00
Andrea Marco Sartori 2db15af24a Merge branch '2.x' into feature/traversable-sequence 2023-08-18 12:33:09 +02:00
Wendell Adriel 8ea7b2b802 Add errors-only flag 2023-08-18 10:13:28 +01:00
Nuno Maduro c9e3932637 Merge pull request #911 from devajmeireles/feature/add-to-be-digits-expectation
[2.x] Introducing `toBeDigits` Expectation
2023-08-18 03:13:59 +00:00
AJ Meireles d218afaf77 introducing new proposal of the PR template 2023-08-17 18:50:23 -03:00
Nuno Maduro 19739ff814 Merge pull request #915 from pestphp/adapting-phpunit-xml-stubs
[2.x] Adapting `phpunit.xml` stubs with PhpUnit
2023-08-17 18:56:50 +00:00
Maurizio 478144fb35 feat: add toHaveStudlyCaseKeys 2023-08-17 20:51:26 +02:00
Maurizio 5d81cf0d4c feat: add toHaveCamelCaseKeys 2023-08-17 20:51:14 +02:00
Maurizio 0b115230f9 feat: add toHaveKebabCaseKeys 2023-08-17 20:50:51 +02:00
Maurizio 0b246f7a76 feat: add toHaveSnakeCaseKeys 2023-08-17 20:50:26 +02:00
AJ Meireles 7914224ff7 introducing https://schema.phpunit.de/10.3/phpunit.xsd 2023-08-17 15:50:15 -03:00
Maurizio 997b0e9368 feat: add toBeStudlyCase 2023-08-17 20:49:40 +02:00
Maurizio a76414aeee feat: add toBeCamelCase 2023-08-17 20:49:21 +02:00
Maurizio d2096df82a feat: add toBeKebabCase 2023-08-17 20:48:51 +02:00
Maurizio 4951b1b0f9 feat: add toBeSnakeCase 2023-08-17 20:48:18 +02:00
AJ Meireles f2e31452f2 Merge pull request #912 from devajmeireles/issue-template
Introducing Issue Template
2023-08-17 11:04:27 -03:00
Nuno Maduro c2985ffb31 release: v2.15.0 2023-08-17 11:28:55 +01:00
Nuno Maduro 492f797dd5 chore: style changes 2023-08-17 11:24:16 +01:00
Nuno Maduro 0b261ef97b feat: adds php@8.3 support 2023-08-17 11:19:43 +01:00
Nuno Maduro f19692a72f chore: changes phpstan settings 2023-08-17 11:19:11 +01:00
Nuno Maduro 0787b37f2c chore: style changes 2023-08-17 11:18:59 +01:00
AJ Meireles f0223b50d0 introducing sample repository input 2023-08-16 15:50:40 -03:00
AJ Meireles 0263fcb2ac wip 2023-08-16 14:18:09 -03:00
AJ Meireles c0a234317b introducing issue template 2023-08-16 14:16:18 -03:00
Nuno Maduro 72100075d2 docs: updates changelog 2023-08-16 09:49:07 +01:00
Nuno Maduro a7aa923241 release: v2.14.1 2023-08-16 09:47:05 +01:00
Nuno Maduro e012517b16 chore: bumps phpunit 2023-08-16 09:46:51 +01:00
Nuno Maduro b1dd18af8a chore: style changes 2023-08-16 09:46:31 +01:00
AJ Meireles 398e3ff3b5 introducing toBeDigits 2023-08-14 17:10:58 -03:00
Nuno Maduro 03648f580c docs: update changelog 2023-08-14 09:44:14 +01:00
Nuno Maduro df2212055b release: v2.14.0 2023-08-14 09:41:14 +01:00
Nuno Maduro b1a137c513 chore: updates snapshot tests 2023-08-14 09:41:05 +01:00
Nuno Maduro 62267dfd3e Merge pull request #906 from JonPurvis/extra-expectations
add expectations for uppercase, lowercase, alpha and alphanumeric
2023-08-13 08:44:17 +00:00
JonPurvis f996a48dfa fix refacto check 2023-08-12 18:14:38 +01:00
JonPurvis 54e00dd4dc add expectations for uppercase, lowercase, alpha and alphanumeric 2023-08-12 16:41:15 +01:00
Nuno Maduro f1414a0beb docs: changelog 2023-08-09 12:16:21 +01:00
Nuno Maduro 47f2ae32c1 release: v2.13.0 2023-08-09 12:14:39 +01:00
Nuno Maduro 306b7eb2a6 feat: adds ddWhen and ddUnless 2023-08-09 12:14:32 +01:00
Nuno Maduro 02f72aabb2 Merge pull request #860 from devajmeireles/feature/add-dd-conditionally
Feature: Introducing The Ability to Dump Conditionally
2023-08-09 10:50:53 +00:00
Nuno Maduro e3a21384e6 release: v2.12.2 2023-08-07 10:29:25 +01:00
Nuno Maduro 331381eed5 release: v2.12.1 2023-08-07 10:26:55 +01:00
Nuno Maduro 75a7d77a80 Updates snapshots 2023-08-07 10:22:58 +01:00
Nuno Maduro cc242a50d1 chore: bump dependencies 2023-08-07 09:39:13 +01:00
Nuno Maduro 704acbf6de Merge pull request #898 from dylanbr/allow_tests_to_be_extended
TestSuiteLoader will always consider classes from the current file
2023-08-06 22:59:52 +00:00
Dylan Bridgman 7baa48e068 TestSuiteLoader will always consider classes from the current file 2023-08-05 13:06:00 +02:00
Nuno Maduro 3742e74adf feat: adds "phpunit/phpunit": "^10.3.1" support 2023-08-04 11:23:55 +01:00
Nuno Maduro cbcea04768 tests: update snapshots 2023-08-03 10:35:38 +01:00
Nuno Maduro f0581f87c6 Merge pull request #896 from fabio-ivona/windows-tests-fix
fix tests
2023-08-03 09:33:17 +00:00
Fabio Ivona 60763f2223 fix tests 2023-08-03 11:27:01 +02:00
Nuno Maduro 8a589022d9 release: v2.12.0 2023-08-02 23:04:35 +01:00
Nuno Maduro 00109e9976 tests: adds more tests regarding snapshots multiple 2023-08-02 23:02:00 +01:00
Nuno Maduro 43107c1743 chore: bumps phpunit 2023-08-02 23:01:36 +01:00
Nuno Maduro 546253d591 Merge pull request #881 from fabio-ivona/snaphsots-cleanup
[2.x] Multiple snapshots
2023-08-02 21:43:56 +00:00
Andrea Marco Sartori d94a6580f5 fix: type check 2023-08-02 20:49:27 +02:00
Andrea Marco Sartori fb75b712d3 chore: update snapshot 2023-08-02 20:49:05 +02:00
Andrea Marco Sartori 6ead2a4e8b feat(sequence): Add support for nested traversable 2023-08-02 20:31:53 +02:00
Fabio Ivona 9afe2296a6 fix line endings on Windows 2023-08-02 10:45:30 +02:00
Fabio Ivona 0221c2a643 refactor 2023-08-01 17:18:55 +02:00
Fabio Ivona 0518971d2f refactor 2023-08-01 17:16:50 +02:00
Fabio Ivona fe3747f850 lint 2023-08-01 17:15:44 +02:00
Fabio Ivona 844d175981 refactor 2023-08-01 17:14:49 +02:00
Fabio Ivona 4e719214c6 fix incomplete tests while updating snapshots 2023-08-01 17:13:15 +02:00
Fabio Ivona 2f6b99885e Merge branch '2.x' into snaphsots-cleanup
# Conflicts:
#	src/Expectation.php
#	src/Expectations/OppositeExpectation.php
2023-08-01 17:04:11 +02:00
Fabio Ivona 4b24da1a58 Merge pull request #892 from ash-jc-allen/comment-updates
Updated comments
2023-08-01 16:35:42 +02:00
Nuno Maduro 72d482de28 docs: update changelog 2023-08-01 14:49:00 +01:00
Ash Allen 2e352c0084 Updated comments. 2023-08-01 13:09:53 +01:00
Fabio Ivona 1efb9de043 multiple snapshots 2023-07-27 11:46:22 +02:00
Fabio Ivona b60d21dfe2 snapshots code cleanup 2023-07-27 11:16:27 +02:00
Fabio Ivona 39e0d61dec phpstan fix 2023-07-27 11:10:42 +02:00
AJ Meireles b00bc4d5ea applying enhancement to use single dd function 2023-07-17 19:11:06 -03:00
AJ Meireles 8abc0d1920 applying enhancement to use ddWhen inside ddUnless 2023-07-17 14:12:54 -03:00
AJ Meireles ea967b439f Feature: Introducing The Ability to Dump Conditionally 2023-07-17 11:08:00 -03:00
488 changed files with 23442 additions and 2518 deletions
+66
View File
@@ -0,0 +1,66 @@
name: Bug Report
description: Report an Issue or Bug with the Pest
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
We're sorry to hear you have a problem. Can you help us solve it by providing the following details.
- type: textarea
id: what-happened
attributes:
label: What Happened
description: What did you expect to happen?
placeholder: When I use expect()->toBeTrue() in my tests, I get an error
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: How to Reproduce
description: How did this occur, please add any config values used and provide a set of reliable steps if possible.
placeholder: Install a fresh Laravel app, add Pest, add a test that uses expect()->toBeTrue()
validations:
required: true
- type: input
id: repository-sample
attributes:
label: Sample Repository
description: If possible, please provide a sample repository that reproduces the issue.
placeholder: https://github.com.br/your-username/your-repository
- type: input
id: pest-version
attributes:
label: Pest Version
description: What version of our Package are you running? Please be as specific as possible
placeholder: 2.14.1
validations:
required: true
- type: input
id: php-version
attributes:
label: PHP Version
description: What version of PHP are you running? Please be as specific as possible
placeholder: 8.1.20
validations:
required: true
- type: dropdown
id: operating-systems
attributes:
label: Operation System
description: On which operating systems does the problem occur? You can select more than one.
multiple: true
options:
- macOS
- Windows
- Linux
validations:
required: true
- type: textarea
id: notes
attributes:
label: Notes
description: Use this field to provide any other notes that you feel might be relevant to the issue.
validations:
required: false
+13 -7
View File
@@ -1,10 +1,16 @@
| Q | A
| ------------- | ---
| Bug fix? | yes/no
| New feature? | yes/no
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
<!--
- Replace this comment by a description of what your PR is solving.
- Fill in the form below correctly. This will help the Pest team to understand the PR and also work on it.
-->
### What:
- [ ] Bug Fix
- [ ] New Feature
### Description:
<!-- describe what your PR is solving -->
### Related:
<!-- link to the issue(s) your PR is solving. If it doesn't exist, remove the "Related" section. -->
+13
View File
@@ -0,0 +1,13 @@
# Security Policy
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
## Reporting a Vulnerability
If you discover a security vulnerability in Pest, please report it privately using one of the following channels:
1. **GitHub Private Vulnerability Reporting** (preferred) — go to the repository's **Security** tab and click **"Report a vulnerability"**. This creates a private advisory visible only to maintainers and provides a structured workflow for triage, fix coordination, and CVE assignment.
2. **Email** — send the details to Nuno Maduro at **enunomaduro@gmail.com**.
All security vulnerabilities will be promptly addressed.
+12
View File
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 5
groups:
github-actions:
patterns:
- "*"
-42
View File
@@ -1,42 +0,0 @@
name: Integration Tests
on:
push:
schedule:
- cron: '0 0 * * *'
jobs:
ci:
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
php: ['8.1', '8.2']
dependency-version: [prefer-lowest, prefer-stable]
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- name: Setup Problem Matches
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install PHP dependencies
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
- name: Integration Tests
run: composer test:integration
+36 -9
View File
@@ -2,9 +2,17 @@ name: Static Analysis
on:
push:
branches: [5.x]
pull_request:
schedule:
- cron: '0 0 * * *'
- cron: '0 9 * * *'
concurrency:
group: static-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
static:
@@ -12,23 +20,45 @@ jobs:
name: Static Tests
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: true
matrix:
dependency-version: [prefer-lowest, prefer-stable]
dependency-version: [prefer-stable]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: 8.1
php-version: 8.4
tools: composer:v2
coverage: none
extensions: sockets
- name: Get Composer cache directory
id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: static-php-8.4-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
restore-keys: |
static-php-8.4-${{ matrix.dependency-version }}-composer-
static-php-8.4-composer-
- name: Install Dependencies
run: composer update --prefer-stable --no-interaction --no-progress --ansi
env:
COMPOSER_ROOT_VERSION: 5.x-dev
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
- name: Profanity Check
run: composer test:profanity
- name: Type Check
run: composer test:type:check
@@ -36,8 +66,5 @@ jobs:
- name: Type Coverage
run: composer test:type:coverage
- name: Refacto
run: composer test:refacto
- name: Style
run: composer test:lint
+44 -13
View File
@@ -2,33 +2,59 @@ name: Tests
on:
push:
branches: [5.x]
pull_request:
schedule:
- cron: '0 0 * * *'
- cron: '0 9 * * *'
concurrency:
group: tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
ci:
tests:
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
php: ['8.1', '8.2']
dependency-version: [prefer-lowest, prefer-stable]
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, macos-latest] # windows-latest
symfony: ['8.0']
php: ['8.4', '8.5']
dependency_version: [prefer-stable]
name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
extensions: sockets
- name: Get Composer cache directory
id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
restore-keys: |
${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-
${{ matrix.os }}-php-${{ matrix.php }}-composer-
- name: Setup Problem Matches
run: |
@@ -36,11 +62,16 @@ jobs:
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install PHP dependencies
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
shell: bash
env:
COMPOSER_ROOT_VERSION: 5.x-dev
run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:^${{ matrix.symfony }}"
- name: Unit Tests
run: composer test:unit
- name: Unit Tests in Parallel
- name: Parallel Tests
run: composer test:parallel
- name: Integration Tests
run: composer test:integration
+2
View File
@@ -12,3 +12,5 @@ coverage.xml
*.swp
*.swo
.vscode/
.STREAM.md
-194
View File
@@ -1,194 +0,0 @@
# Release Notes for 2.x
## Unreleased
## [v2.10.1 (2023-07-31)](https://github.com/pestphp/pest/compare/v2.10.0...v2.10.1)
### Fixed
- `not->toHaveSuffix` and `toHavePrefix` expectations ([#888](https://github.com/pestphp/pest/pull/888))
## [v2.10.0 (2023-07-31)](https://github.com/pestphp/pest/compare/v2.9.5...v2.10.0)
### Added
- `repeat` feature ([f3f35a2](https://github.com/pestphp/pest/commit/f3f35a2ed119f63eefd323a8c66d3387e908df3f))
### Fixed
- `-v` option ([86a6b32](https://github.com/pestphp/pest/commit/86a6b3271518742dc39761228687a5107551d279))
## [v2.9.5 (2023-07-22)](https://github.com/pestphp/pest/compare/v2.9.4...v2.9.5)
### Fixed
- Assertions count on arch expectations ([632ffc2](https://github.com/pestphp/pest/commit/632ffc2f8e1fe45f739b12b818426ae14700079e))
## [v2.9.4 (2023-07-22)](https://github.com/pestphp/pest/compare/v2.9.3...v2.9.4)
### Fixed
- Output on `describe` `beforeEach` failure ([5637dfa](https://github.com/pestphp/pest/commit/5637dfa75d1a331adc810935536cde7c3196af06))
## [v2.9.3 (2023-07-20)](https://github.com/pestphp/pest/compare/v2.9.2...v2.9.3)
### Fixed
- Snapshots directory on Windows environments ([cf52752](https://github.com/pestphp/pest/commit/cf5275293fe693ec2cf4dbadbadae01daaa08169))
## [v2.9.2 (2023-07-20)](https://github.com/pestphp/pest/compare/v2.9.1...v2.9.2)
### Fixed
- `beforeEach` on Windows environments ([a37a3b9](https://github.com/pestphp/pest/commit/a37a3b9f9931bc1ab1ea9e1d6d38dfb55dde3f74))
## [v2.9.1 (2023-07-20)](https://github.com/pestphp/pest/compare/v2.9.0...v2.9.1)
### Chore
- Bumps PHPUnit to `^10.2.6` ([8fdb0b3](https://github.com/pestphp/pest/commit/8fdb0b3d32ce9ee12bd182f22751c2b41a53e97b))
## [v2.9.0 (2023-07-19)](https://github.com/pestphp/pest/compare/v2.8.1...v2.9.0)
> "Spicy Summer" is the codename assigned to Pest 2.9, for full details check our announcement: [https://pestphp.com/docs/pest-spicy-summer-release](https://pestphp.com/docs/pest-spicy-summer-release)
### Added
- Built-in Snapshot Testing ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
- Describe Blocks ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
- Architectural Testing++ ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
- Type Coverage Plugin ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
- Drift Plugin ([c828756](https://github.com/pestphp/pest/commit/c8287567eb8c3dbea5845b2a6f70804b094b4b60))
## [v2.8.1 (2023-06-20)](https://github.com/pestphp/pest/compare/v2.8.0...v2.8.1)
### Fixed
- Fixes "Cannot find TestCase object on call stack" ([eb7bb34](https://github.com/pestphp/pest/commit/eb7bb348253f412e806a6ba6f0df46c0435d0dfe))
## [v2.8.0 (2023-06-19)](https://github.com/pestphp/pest/compare/v2.7.0...v2.8.0)
### Added
- Support for `globs` in `uses` ([#829](https://github.com/pestphp/pest/pull/829))
## [v2.7.0 (2023-06-15)](https://github.com/pestphp/pest/compare/v2.6.3...v2.7.0)
### Added
- Support for unexpected output on printer ([eb9f31e](https://github.com/pestphp/pest/commit/eb9f31edeb00a88c449874f3d48156128a00fff8))
### Chore
- Bumps PHPUnit to `^10.2.2` ([0e5470b](https://github.com/pestphp/pest/commit/0e5470b192b259ba2db7c02a50371216c98fc0a6))
## [v2.6.3 (2023-06-07)](https://github.com/pestphp/pest/compare/v2.6.2...v2.6.3)
### Chore
- Bumps PHPUnit to `^10.2.1` ([73a859e](https://github.com/pestphp/pest/commit/73a859ee563fe96944ba39b191dceca28ef703c2))
## [v2.6.2 (2023-06-02)](https://github.com/pestphp/pest/compare/v2.6.1...v2.6.2)
### Chore
- Bumps PHPUnit to `^10.2.0` ([a0041f1](https://github.com/pestphp/pest/commit/a0041f139cba94fe5d15318c38e275f2e2fb3350))
## [v2.6.1 (2023-04-12)](https://github.com/pestphp/pest/compare/v2.6.0...v2.6.1)
### Fixes
- PHPStorm issue output problem for tests throwing an exception before the first assertion ([#809](https://github.com/pestphp/pest/pull/809))
- Allow traits to be covered ([#804](https://github.com/pestphp/pest/pull/804))
### Chore
- Bumps PHPUnit to `^10.1.3` ([c993252](https://github.com/pestphp/pest/commit/c99325275acf1fd3759b487b93ec50473f706709))
## [v2.6.0 (2023-04-05)](https://github.com/pestphp/pest/compare/v2.5.2...v2.6.0)
### Adds
- Allows `toThrow` to be used against an exception instance ([#797](https://github.com/pestphp/pest/pull/797))
## [v2.5.2 (2023-04-19)](https://github.com/pestphp/pest/compare/v2.5.1...v2.5.2)
### Chore
- Removes `myclabs/php-enuma` dependency ([1a05df1](https://github.com/pestphp/pest/commit/1a05df14d0ce7d12583df26ff716807db6f81f13))
## [v2.5.1 (2023-04-18)](https://github.com/pestphp/pest/compare/v2.5.0...v2.5.1)
### Chore
- Bumps PHPUnit to `^10.1.1` ([ec6a817](https://github.com/pestphp/pest/commit/ec6a81735af19f5463d24545df97535d77697ec6))
## [v2.5.0 (2023-04-14)](https://github.com/pestphp/pest/compare/v2.4.0...v2.5.0)
### Chore
- Bumps PHPUnit to `^10.1.0` ([#780](https://github.com/pestphp/pest/pull/780))
## [v2.4.0 (2023-04-03)](https://github.com/pestphp/pest/compare/v2.3.0...v2.4.0)
### Added
- `skipOnWindows()`, `skipOnMac()`, and `skipOnLinux()` ([#757](https://github.com/pestphp/pest/pull/757))
- source architecture testing violation ([#1](https://github.com/pestphp/pest-plugin-arch/pull/1))([8e66263](https://github.com/pestphp/pest-plugin-arch/commit/8e66263104304e99e3d6ceda25c7ed679b27fb03))
- `toHaveProperties` may now also check values ([#760](https://github.com/pestphp/pest/pull/760))
### Fixed
- Tests on `tests/Helpers` directory not being executed ([#753](https://github.com/pestphp/pest/pull/753))
- Teamcity count ([#747](https://github.com/pestphp/pest/pull/747))
- Parallel execution when class extends class with same name ([#748](https://github.com/pestphp/pest/pull/748))
- Wording on `uses()` hint ([#745](https://github.com/pestphp/pest/pull/745/files))
## [v2.3.0 (2023-03-28)](https://github.com/pestphp/pest/compare/v2.2.3...v2.3.0)
### Added
- Better error handler about missing uses ([#743](https://github.com/pestphp/pest/pull/743))
### Fixed
- Inconsistent spelling of `dataset` ([#739](https://github.com/pestphp/pest/pull/739))
### Chore
- Bumps PHPUnit to `^10.0.19` ([3d7e621](https://github.com/pestphp/pest/commit/3d7e621b7dfc03f0b2d9dcf6eb06c26bc383f502))
## [v2.2.3 (2023-03-24)](https://github.com/pestphp/pest/compare/v2.2.2...v2.2.3)
### Fixed
- Unnecessary dataset on dataset arguments mismatch ([#736](https://github.com/pestphp/pest/pull/736))
- Parallel arguments on plugins order ([#703](https://github.com/pestphp/pest/pull/703))
- Arch plugin runtime exceptions on bad phpdocs ([2f2b51c](https://github.com/pestphp/pest/commit/2f2b51ce3d1b000be9d6add0e785fd0044931b3b))
## [v2.2.2 (2023-03-23)](https://github.com/pestphp/pest/compare/v2.2.1...v2.2.2)
### Fixed
- Edge case in parallel execution test description ([3ce6408](https://github.com/pestphp/pest/commit/3ce640819541ca6022b250e000f336d87c3e7889))
## [v2.2.1 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.2.0...v2.2.1)
### Fixed
- Collision between tests names with underscores ([#724](https://github.com/pestphp/pest/pull/724))
### Chore
- Bumps PHPUnit to `^10.0.18` ([1408cff](https://github.com/pestphp/pest/commit/1408cffc028690057e44f00038f9390f776e6bfb))
## [v2.2.0 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.1.0...v2.2.0)
### Added
- 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))
### Fixed
- `--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)
### Added
- `only` test case method ([bcd1503](https://github.com/pestphp/pest/commit/bcd1503cade938853a55c1283b02b6b820ea0b69))
### Fixed
- Issues with different characters on test names ([715](https://github.com/pestphp/pest/pull/715))
## [v2.0.2 (2023-03-20)](https://github.com/pestphp/pest/compare/v2.0.1...v2.0.2)
### Fixed
- `Pest.php` not being loaded in certain scenarios ([b887116](https://github.com/pestphp/pest/commit/b887116e5ce9a69403ad620cad20f0a029474eb5))
## [v2.0.1 (2023-03-20)](https://github.com/pestphp/pest/compare/v2.0.0...v2.0.1)
### Fixed
- Wrong `version` configuration key on `composer.json` ([8f91f40](https://github.com/pestphp/pest/commit/8f91f40e8ea8b35e04b7989bed6a8f9439e2a2d6))
## [v2.0.0 (2023-03-20)](https://github.com/pestphp/pest/compare/v1.22.6...v2.0.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.
+2 -2
View File
@@ -42,7 +42,7 @@ composer test
Check types:
```bash
composer test:types
composer test:type:check
```
Unit tests:
@@ -69,7 +69,7 @@ If you want to check things work against a specific version of PHP, you may incl
the `PHP` build argument when building the image:
```bash
make build ARGS="--build-arg PHP=8.2"
make build ARGS="--build-arg PHP=8.3"
```
The default PHP version will always be the lowest version of PHP supported by Pest.
-14
View File
@@ -1,14 +0,0 @@
# 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
+30 -15
View File
@@ -1,39 +1,54 @@
<p align="center">
<img src="https://raw.githubusercontent.com/pestphp/art/master/v2/banner.png" width="600" alt="PEST">
<img src="https://raw.githubusercontent.com/pestphp/art/master/v5/social.png" width="600" alt="PEST">
<p align="center">
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=2.x&label=Tests%202.x"></a>
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (5.x)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=5.x&label=Tests%205.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="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://whyphp.dev"><img src="https://img.shields.io/badge/Why_PHP-in_2026-7A86E8?style=flat-square&labelColor=18181b" alt="Why PHP in 2026"></a>
<a href="https://youtube.com/@nunomaduro?sub_confirmation=1"><img alt="YouTube Channel Subscribers" src="https://img.shields.io/youtube/channel/subscribers/UCO_hYZF2gb_CyG5sA7ArlGg?style=flat&label=youtube&color=brightgreen"></a>
</p>
</p>
------
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest-v5-is-here)**.
**Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
- Follow us on Twitter at **[@pestphp »](https://twitter.com/pestphp)**
- Join us at **[discord.gg/kaHY6p54JH »](https://discord.gg/kaHY6p54JH)** or **[t.me/+kYH5G4d5MV83ODk0 »](https://t.me/+kYH5G4d5MV83ODk0)**
- Follow the creator Nuno Maduro:
- YouTube: **[youtube.com/@nunomaduro](https://youtube.com/@nunomaduro)** — Videos every week
- Twitch: **[twitch.tv/nunomaduro](https://twitch.tv/nunomaduro)** — Live coding on Mondays, Wednesdays, and Fridays at 9PM UTC
- Twitter / X: **[x.com/enunomaduro](https://x.com/enunomaduro)**
- LinkedIn: **[linkedin.com/in/nunomaduro](https://www.linkedin.com/in/nunomaduro)**
- Instagram: **[instagram.com/enunomaduro](https://www.instagram.com/enunomaduro)**
- Tiktok: **[tiktok.com/@enunomaduro](https://www.tiktok.com/@enunomaduro)**
## Sponsors
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/)**
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
- **[Mailtrap](https://l.rw.rw/pestphp)**
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
- **[Tighten](https://tighten.com/?ref=nunomaduro)**
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
### Gold Sponsors
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
### Premium Sponsors
- [Akaunting](https://akaunting.com)
- [Codecourse](https://codecourse.com/)
- [Laracasts](https://laracasts.com/)
- [Localazy](https://localazy.com)
- [Fathom Analytics](https://usefathom.com/)
- [Meema](https://meema.io)
- [Zapiet](https://www.zapiet.com)
- [Zapiet](https://zapiet.com/?ref=pestphp)
- [Load Forge](https://loadforge.com/?ref=pestphp)
- [Route4Me](https://route4me.com/pt?ref=pestphp)
- [Nerdify](https://getnerdify.com/?ref=pestphp)
- [Akaunting](https://akaunting.com/?ref=pestphp)
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
+4 -4
View File
@@ -2,13 +2,13 @@
When releasing a new version of Pest there are some checks and updates that need to be done:
> **For Pest v1 you should use the `1.x` branch instead.**
> **For Pest v4 you should use the `4.x` branch instead.**
- 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
- Clear your local repository with: `git add . && git reset --hard && git checkout 5.x`
- On the GitHub repository, check the contents of [github.com/pestphp/pest/compare/{latest_version}...5.x](https://github.com/pestphp/pest/compare/{latest_version}...5.x)
- Update the version number in [src/Pest.php](src/Pest.php)
- Run the tests locally using: `composer test`
- Commit the CHANGELOG and Pest file with the message: `git commit -m "release: vX.X.X"`
- Commit the Pest file with the message: `git commit -m "release: vX.X.X"`
- Push the changes to GitHub
- Check that the CI is passing as expected: [github.com/pestphp/pest/actions](https://github.com/pestphp/pest/actions)
- Tag and push the tag with `git tag vX.X.X && git push --tags`
+132 -16
View File
@@ -1,9 +1,18 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
<?php
declare(strict_types=1);
use Pest\Contracts\Restarter;
use Pest\Kernel;
use Pest\Panic;
use Pest\Support\Container;
use Pest\TestCaseFilters\GitDirtyTestCaseFilter;
use Pest\TestCaseMethodFilters\AssigneeTestCaseFilter;
use Pest\TestCaseMethodFilters\IssueTestCaseFilter;
use Pest\TestCaseMethodFilters\NotesTestCaseFilter;
use Pest\TestCaseMethodFilters\PrTestCaseFilter;
use Pest\TestCaseMethodFilters\FlakyTestCaseFilter;
use Pest\TestCaseMethodFilters\TodoTestCaseFilter;
use Pest\TestSuite;
use Symfony\Component\Console\Input\ArgvInput;
@@ -13,39 +22,108 @@ use Symfony\Component\Console\Output\ConsoleOutput;
// Ensures Collision's Printer is registered.
$_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter';
$args = $_SERVER['argv'];
$arguments = $originalArguments = $_SERVER['argv'];
$dirty = false;
$todo = false;
$flaky = false;
$notes = false;
foreach ($arguments as $key => $value) {
foreach ($args as $key => $value) {
if ($value === '--compact') {
$_SERVER['COLLISION_PRINTER_COMPACT'] = 'true';
unset($args[$key]);
unset($arguments[$key]);
}
if ($value === '--profile') {
$_SERVER['COLLISION_PRINTER_PROFILE'] = 'true';
unset($args[$key]);
unset($arguments[$key]);
}
if (str_contains($value, '--test-directory')) {
unset($args[$key]);
if (str_contains($value, '--test-directory=')) {
unset($arguments[$key]);
} elseif ($value === '--test-directory') {
unset($arguments[$key]);
if (isset($arguments[$key + 1])) {
unset($arguments[$key + 1]);
}
}
if ($value === '--dirty') {
$dirty = true;
unset($args[$key]);
unset($arguments[$key]);
}
if ($value === '--todos') {
if (in_array($value, ['--todo', '--todos'], true)) {
$todo = true;
unset($args[$key]);
unset($arguments[$key]);
}
if ($value === '--flaky') {
$flaky = true;
unset($arguments[$key]);
}
if ($value === '--notes') {
$notes = true;
unset($arguments[$key]);
}
if (str_contains($value, '--assignee=')) {
unset($arguments[$key]);
} elseif ($value === '--assignee') {
unset($arguments[$key]);
if (isset($arguments[$key + 1])) {
unset($arguments[$key + 1]);
}
}
if (str_contains($value, '--issue=')) {
unset($arguments[$key]);
} elseif ($value === '--issue') {
unset($arguments[$key]);
if (isset($arguments[$key + 1])) {
unset($arguments[$key + 1]);
}
}
if (str_contains($value, '--ticket=')) {
unset($arguments[$key]);
} elseif ($value === '--ticket') {
unset($arguments[$key]);
if (isset($arguments[$key + 1])) {
unset($arguments[$key + 1]);
}
}
if (str_contains($value, '--pr=')) {
unset($arguments[$key]);
} elseif ($value === '--pr') {
unset($arguments[$key]);
if (isset($arguments[$key + 1])) {
unset($arguments[$key + 1]);
}
}
if (str_contains($value, '--pull-request=')) {
unset($arguments[$key]);
} elseif ($value === '--pull-request') {
unset($arguments[$key]);
if (isset($arguments[$key + 1])) {
unset($arguments[$key + 1]);
}
}
if (str_contains($value, '--teamcity')) {
unset($args[$key]);
$args[] = '--no-output';
unset($arguments[$key]);
$arguments[] = '--no-output';
unset($_SERVER['COLLISION_PRINTER']);
}
}
@@ -66,7 +144,8 @@ use Symfony\Component\Console\Output\ConsoleOutput;
// Get $rootPath based on $autoloadPath
$rootPath = dirname($autoloadPath, 2);
$input = new ArgvInput();
$input = new ArgvInput;
$testSuite = TestSuite::getInstance(
$rootPath,
@@ -78,7 +157,35 @@ use Symfony\Component\Console\Output\ConsoleOutput;
}
if ($todo) {
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter());
$testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter);
}
if ($flaky) {
$testSuite->tests->addTestCaseMethodFilter(new FlakyTestCaseFilter);
}
if ($notes) {
$testSuite->tests->addTestCaseMethodFilter(new NotesTestCaseFilter);
}
if ($assignee = $input->getParameterOption('--assignee')) {
$testSuite->tests->addTestCaseMethodFilter(new AssigneeTestCaseFilter((string) $assignee));
}
if ($issue = $input->getParameterOption('--issue')) {
$testSuite->tests->addTestCaseMethodFilter(new IssueTestCaseFilter((int) $issue));
}
if ($issue = $input->getParameterOption('--ticket')) {
$testSuite->tests->addTestCaseMethodFilter(new IssueTestCaseFilter((int) $issue));
}
if ($pr = $input->getParameterOption('--pr')) {
$testSuite->tests->addTestCaseMethodFilter(new PrTestCaseFilter((int) $pr));
}
if ($pr = $input->getParameterOption('--pull-request')) {
$testSuite->tests->addTestCaseMethodFilter(new PrTestCaseFilter((int) $pr));
}
$isDecorated = $input->getParameterOption('--colors', 'always') !== 'never';
@@ -88,9 +195,18 @@ use Symfony\Component\Console\Output\ConsoleOutput;
try {
$kernel = Kernel::boot($testSuite, $input, $output);
$result = $kernel->handle($args);
$container = Container::getInstance();
$kernel->shutdown();
foreach (Kernel::RESTARTERS as $restarterClass) {
$restarter = $container->get($restarterClass);
assert($restarter instanceof Restarter);
$restarter->maybeRestart($rootPath, $originalArguments);
}
$result = $kernel->handle($originalArguments, $arguments);
$kernel->terminate();
} catch (Throwable|Error $e) {
Panic::with($e);
}
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/env node
import { readdir, readFile } from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { createRequire } from 'node:module'
import { resolve, relative, extname, sep, join } from 'node:path'
import { pathToFileURL } from 'node:url'
const PAGE_EXTENSIONS = new Set([
'.vue', '.svelte',
'.tsx', '.jsx',
'.ts', '.js',
'.mts', '.cts', '.mjs', '.cjs',
])
const ASSET_EXT_RE = /\.(css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|eot|otf|md|mdx|txt|html|mp4|webm|mp3|wav|ogg|m4a|pdf|wasm|glsl|frag|vert)$/i
const PROJECT_ROOT = resolve(process.argv[2] ?? process.cwd())
const PAGE_DIR_CANDIDATES = [
'resources/js/Pages',
'resources/js/pages',
'assets/js/Pages',
'assets/js/pages',
'assets/Pages',
'assets/pages',
]
async function loadRolldown() {
const projectRequire = createRequire(join(PROJECT_ROOT, 'package.json'))
const path = projectRequire.resolve('rolldown')
return await import(pathToFileURL(path).href)
}
async function readJsonWithComments(path) {
const raw = await readFile(path, 'utf8')
const stripped = raw
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/[^\n]*/g, '$1')
return JSON.parse(stripped)
}
async function loadAliasFromTsconfig() {
const alias = {}
for (const name of ['tsconfig.json', 'jsconfig.json']) {
const p = join(PROJECT_ROOT, name)
if (!existsSync(p)) continue
let cfg
try { cfg = await readJsonWithComments(p) } catch { continue }
const baseUrl = resolve(PROJECT_ROOT, cfg?.compilerOptions?.baseUrl ?? '.')
const paths = cfg?.compilerOptions?.paths ?? {}
for (const [key, targets] of Object.entries(paths)) {
if (!key.endsWith('/*')) continue
const t0 = Array.isArray(targets) ? targets[0] : null
if (typeof t0 !== 'string' || !t0.endsWith('/*')) continue
const aliasKey = key.slice(0, -2)
if (alias[aliasKey] !== undefined) continue
alias[aliasKey] = resolve(baseUrl, t0.slice(0, -2))
}
}
return alias
}
async function listPageFiles(pagesDir) {
if (!existsSync(pagesDir)) return []
const out = []
const walk = async (dir) => {
let entries
try { entries = await readdir(dir, { withFileTypes: true }) } catch { return }
for (const entry of entries) {
const full = resolve(dir, entry.name)
if (entry.isDirectory()) { await walk(full); continue }
if (PAGE_EXTENSIONS.has(extname(entry.name))) out.push(full)
}
}
await walk(pagesDir)
return out
}
async function discoverPagesDir() {
const override = process.env.TIA_VITE_PAGES_DIR
if (override && override.length > 0) {
return resolve(PROJECT_ROOT, override.replace(/\\/g, '/'))
}
for (const rel of PAGE_DIR_CANDIDATES) {
const abs = resolve(PROJECT_ROOT, rel)
if (!existsSync(abs)) continue
const files = await listPageFiles(abs)
if (files.length > 0) return abs
}
return null
}
function componentNameFor(pageAbs, pagesDir) {
const rel = relative(pagesDir, pageAbs).split(sep).join('/')
const ext = extname(rel)
return rel.slice(0, rel.length - ext.length)
}
function isLocalSpecifier(source, aliasKeys) {
if (source.startsWith('.') || source.startsWith('/')) return true
for (const key of aliasKeys) {
if (source === key || source.startsWith(key + '/')) return true
}
return false
}
async function main() {
const pagesDir = await discoverPagesDir()
if (pagesDir === null) {
process.stdout.write('{}')
return
}
const pages = await listPageFiles(pagesDir)
if (pages.length === 0) {
process.stdout.write('{}')
return
}
const { rolldown } = await loadRolldown()
const alias = await loadAliasFromTsconfig()
const aliasKeys = Object.keys(alias)
const graph = new Map()
const collector = {
name: 'pest-tia-collector',
moduleParsed(info) {
const id = info.id
if (!id || id.startsWith('\0')) return
const deps = new Set()
for (const i of info.importedIds) if (i && !i.startsWith('\0')) deps.add(i)
for (const i of info.dynamicallyImportedIds) if (i && !i.startsWith('\0')) deps.add(i)
graph.set(id, deps)
},
}
const externalBare = {
name: 'pest-tia-external-bare',
resolveId(source) {
if (!source) return null
if (isLocalSpecifier(source, aliasKeys)) return null
return { id: source, external: true }
},
}
const assetStub = {
name: 'pest-tia-asset-stub',
load(id) {
if (!id) return null
if (ASSET_EXT_RE.test(id)) {
return { code: 'export default null', moduleSideEffects: false }
}
return null
},
}
const input = Object.create(null)
for (let i = 0; i < pages.length; i++) input[`p${i}`] = pages[i]
const bundle = await rolldown({
input,
cwd: PROJECT_ROOT,
resolve: {
alias,
extensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.json'],
},
transform: { jsx: 'preserve' },
treeshake: false,
plugins: [externalBare, assetStub, collector],
logLevel: 'silent',
onLog: () => {},
})
try {
await bundle.generate({ format: 'esm' })
} finally {
await bundle.close()
}
const reverse = new Map()
const transitiveCache = new Map()
const computeTransitive = (id, stack) => {
const cached = transitiveCache.get(id)
if (cached) return cached
if (stack.has(id)) return null
stack.add(id)
const acc = new Set()
const deps = graph.get(id)
if (deps) {
for (const dep of deps) {
if (!dep || dep.startsWith('\0')) continue
if (dep.startsWith(PROJECT_ROOT)) {
const rel = relative(PROJECT_ROOT, dep).split(sep).join('/')
acc.add(rel)
}
if (stack.has(dep)) continue
const child = computeTransitive(dep, stack)
if (child) for (const r of child) acc.add(r)
}
}
stack.delete(id)
transitiveCache.set(id, acc)
return acc
}
for (const page of pages) {
const pageComponent = componentNameFor(page, pagesDir)
const reachable = computeTransitive(page, new Set())
if (!reachable) continue
for (const rel of reachable) {
const bucket = reverse.get(rel) ?? new Set()
bucket.add(pageComponent)
reverse.set(rel, bucket)
}
}
const payload = Object.create(null)
const keys = [...reverse.keys()].sort()
for (const key of keys) {
payload[key] = [...reverse.get(key)].sort()
}
process.stdout.write(JSON.stringify(payload))
}
try {
void pathToFileURL
await main()
} catch (err) {
process.stderr.write(String(err?.stack ?? err ?? 'unknown error'))
process.exit(1)
}
+21 -5
View File
@@ -6,13 +6,14 @@ use ParaTest\WrapperRunner\ApplicationForWrapperWorker;
use ParaTest\WrapperRunner\WrapperWorker;
use Pest\Kernel;
use Pest\Plugins\Actions\CallsHandleArguments;
use Pest\Support\Container;
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();
$workerArgv = new ArgvInput;
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
$testSuite = TestSuite::getInstance($rootPath, $workerArgv->getParameterOption(
@@ -20,7 +21,7 @@ $bootPest = (static function (): void {
'tests'
));
$input = new ArgvInput();
$input = new ArgvInput;
$output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true);
@@ -32,10 +33,13 @@ $bootPest = (static function (): void {
'status-file:',
'progress-file:',
'unexpected-output-file:',
'testresult-file:',
'test-result-file:',
'result-cache-file:',
'teamcity-file:',
'testdox-file:',
'testdox-color',
'testdox-columns:',
'testdox-summary',
'phpunit-argv:',
]);
@@ -55,13 +59,23 @@ $bootPest = (static function (): void {
}
}
$container = Container::getInstance();
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);
foreach (Kernel::RESTARTERS as $restarterClass) {
$restarter = $container->get($restarterClass);
$restarter->maybeRestart($rootPath, $_SERVER['argv']);
}
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['unexpected-output-file']) && is_string($getopt['unexpected-output-file']));
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
assert(isset($getopt['test-result-file']) && is_string($getopt['test-result-file']));
assert(! isset($getopt['result-cache-file']) || is_string($getopt['result-cache-file']));
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
@@ -77,10 +91,12 @@ $bootPest = (static function (): void {
$phpunitArgv,
$getopt['progress-file'],
$getopt['unexpected-output-file'],
$getopt['testresult-file'],
$getopt['test-result-file'],
$getopt['result-cache-file'] ?? null,
$getopt['teamcity-file'] ?? null,
$getopt['testdox-file'] ?? null,
isset($getopt['testdox-color']),
(int) ($getopt['testdox-columns'] ?? null),
);
while (true) {
+47 -23
View File
@@ -17,17 +17,22 @@
}
],
"require": {
"php": "^8.1.0",
"brianium/paratest": "^7.2.3",
"nunomaduro/collision": "^7.7.0",
"nunomaduro/termwind": "^1.15.1",
"pestphp/pest-plugin": "^2.0.1",
"pestphp/pest-plugin-arch": "^2.2.3",
"phpunit/phpunit": "^10.2.6"
"php": "^8.4",
"brianium/paratest": "^7.22.4",
"nunomaduro/collision": "^8.9.4",
"nunomaduro/termwind": "^2.4.0",
"pestphp/pest-plugin": "^5.0.0",
"pestphp/pest-plugin-arch": "^5.0.0",
"pestphp/pest-plugin-mutate": "^5.0.0",
"pestphp/pest-plugin-profanity": "^5.0.0",
"phpunit/phpunit": "^13.2.3",
"symfony/process": "^8.1.0"
},
"conflict": {
"webmozart/assert": "<1.11.0",
"phpunit/phpunit": ">10.2.6"
"filp/whoops": "<2.18.3",
"phpunit/phpunit": ">13.2.3",
"sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0"
},
"autoload": {
"psr-4": {
@@ -45,14 +50,20 @@
"Tests\\Fixtures\\Arch\\": "tests/Fixtures/Arch",
"Tests\\": "tests/PHPUnit/"
},
"classmap": [
"tests/Fixtures/Arch/ToBeCasedCorrectly/IncorrectCasing/incorrectCasing.php"
],
"files": [
"tests/Autoload.php"
]
},
"require-dev": {
"pestphp/pest-dev-tools": "^2.12.0",
"pestphp/pest-plugin-type-coverage": "^2.0.0",
"symfony/process": "^6.3.2"
"mrpunyapal/peststan": "^0.2.11",
"laravel/pao": "^1.1.1",
"pestphp/pest-dev-tools": "^5.0.0",
"pestphp/pest-plugin-browser": "^5.0.0",
"pestphp/pest-plugin-type-coverage": "^5.0.0",
"psy/psysh": "^0.12.24"
},
"minimum-stability": "dev",
"prefer-stable": true,
@@ -67,19 +78,23 @@
"bin/pest"
],
"scripts": {
"refacto": "rector",
"lint": "pint",
"test:refacto": "rector --dry-run",
"test:lint": "pint --test",
"lint": [
"rector",
"pint --parallel"
],
"test:lint": [
"rector --dry-run",
"pint --parallel --test"
],
"test:profanity": "php bin/pest --profanity --compact",
"test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug",
"test:type:coverage": "php bin/pest --type-coverage --min=100",
"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",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always --update-snapshots",
"test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100",
"test:unit": "php bin/pest --exclude-group=integration --compact",
"test:inline": "php bin/pest --configuration=phpunit.inline.xml",
"test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3",
"test:integration": "php bin/pest --group=integration -v",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots",
"test": [
"@test:refacto",
"@test:lint",
"@test:type:check",
"@test:type:coverage",
@@ -91,6 +106,8 @@
"extra": {
"pest": {
"plugins": [
"Pest\\Mutate\\Plugins\\Mutate",
"Pest\\Plugins\\Configuration",
"Pest\\Plugins\\Bail",
"Pest\\Plugins\\Cache",
"Pest\\Plugins\\Coverage",
@@ -106,8 +123,15 @@
"Pest\\Plugins\\Snapshot",
"Pest\\Plugins\\Verbose",
"Pest\\Plugins\\Version",
"Pest\\Plugins\\Shard",
"Pest\\Plugins\\Tia",
"Pest\\Plugins\\Parallel"
]
},
"phpstan": {
"includes": [
"extension.neon"
]
}
}
}
-14
View File
@@ -1,14 +0,0 @@
version: "3.8"
services:
php:
build:
context: ./docker
volumes:
- .:/var/www/html
composer:
build:
context: ./docker
volumes:
- .:/var/www/html
entrypoint: ["composer"]
+4 -11
View File
@@ -1,21 +1,14 @@
ARG PHP=8.1
FROM php:${PHP}-cli-alpine
RUN apk update \
&& apk add zip libzip-dev icu-dev git
RUN apk update && apk add \
zip libzip-dev icu-dev git
RUN docker-php-ext-configure zip
RUN docker-php-ext-install zip
RUN docker-php-ext-enable zip
RUN docker-php-ext-install zip intl
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 apk add --no-cache linux-headers autoconf build-base
RUN pecl install xdebug
RUN docker-php-ext-enable xdebug
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
+4
View File
@@ -0,0 +1,4 @@
parameters:
universalObjectCratesClasses:
- Pest\Support\HigherOrderTapProxy
- Pest\Expectation
+37 -3
View File
@@ -1,5 +1,37 @@
<?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);
/*
@@ -20,9 +52,11 @@ use PHPUnit\Util\Filter;
use PHPUnit\Util\ThrowableToStringMapper;
/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class ThrowableBuilder
final readonly class ThrowableBuilder
{
/**
* @throws Exception
@@ -36,7 +70,7 @@ final class ThrowableBuilder
$previous = self::from($previous);
}
$trace = Filter::getFilteredStacktrace($t);
$trace = Filter::stackTraceFromThrowableAsString($t);
if ($t instanceof RenderableOnCollisionEditor && $frame = $t->toCollisionEditor()) {
$file = $frame->getFile();
@@ -50,7 +84,7 @@ final class ThrowableBuilder
$t->getMessage(),
ThrowableToStringMapper::map($t),
$trace,
$previous
$previous,
);
}
}
+505
View File
@@ -0,0 +1,505 @@
<?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\Logging\JUnit;
use DOMDocument;
use DOMElement;
use Pest\Logging\Converter;
use Pest\Support\Container;
use Pest\TestSuite;
use PHPUnit\Event\Code\Test;
use PHPUnit\Event\Code\TestMethod;
use PHPUnit\Event\EventFacadeIsSealedException;
use PHPUnit\Event\Facade;
use PHPUnit\Event\InvalidArgumentException;
use PHPUnit\Event\Telemetry\HRTime;
use PHPUnit\Event\Telemetry\Info;
use PHPUnit\Event\Test\Errored;
use PHPUnit\Event\Test\Failed;
use PHPUnit\Event\Test\Finished;
use PHPUnit\Event\Test\MarkedIncomplete;
use PHPUnit\Event\Test\PreparationStarted;
use PHPUnit\Event\Test\Prepared;
use PHPUnit\Event\Test\PrintedUnexpectedOutput;
use PHPUnit\Event\Test\Skipped;
use PHPUnit\Event\TestSuite\Started;
use PHPUnit\Event\UnknownSubscriberTypeException;
use PHPUnit\TextUI\Output\Printer;
use PHPUnit\Util\Xml;
use function assert;
use function basename;
use function is_int;
use function sprintf;
use function str_replace;
use function trim;
/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class JunitXmlLogger
{
private readonly Printer $printer;
private readonly Converter $converter; // pest-added
private DOMDocument $document;
private DOMElement $root;
/**
* @var DOMElement[]
*/
private array $testSuites = [];
/**
* @var array<int,int>
*/
private array $testSuiteTests = [0];
/**
* @var array<int,int>
*/
private array $testSuiteAssertions = [0];
/**
* @var array<int,int>
*/
private array $testSuiteErrors = [0];
/**
* @var array<int,int>
*/
private array $testSuiteFailures = [0];
/**
* @var array<int,int>
*/
private array $testSuiteSkipped = [0];
/**
* @var array<int,int>
*/
private array $testSuiteTimes = [0];
private int $testSuiteLevel = 0;
private ?DOMElement $currentTestCase = null;
private ?HRTime $time = null;
private bool $prepared = false;
private bool $preparationFailed = false;
/**
* @throws EventFacadeIsSealedException
* @throws UnknownSubscriberTypeException
*/
public function __construct(Printer $printer, Facade $facade)
{
$this->printer = $printer;
$this->converter = new Converter(Container::getInstance()->get(TestSuite::class)->rootPath); // pest-added
$this->registerSubscribers($facade);
$this->createDocument();
}
public function flush(): void
{
$this->printer->print($this->document->saveXML() ?: '');
$this->printer->flush();
}
public function testSuiteStarted(Started $event): void
{
$testSuite = $this->document->createElement('testsuite');
$testSuite->setAttribute('name', $this->converter->getTestSuiteName($event->testSuite())); // pest-changed
if ($event->testSuite()->isForTestClass()) {
$testSuite->setAttribute('file', $this->converter->getTestSuiteLocation($event->testSuite()) ?? ''); // pest-changed
}
if ($this->testSuiteLevel > 0) {
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
} else {
$this->root->appendChild($testSuite);
}
$this->testSuiteLevel++;
$this->testSuites[$this->testSuiteLevel] = $testSuite;
$this->testSuiteTests[$this->testSuiteLevel] = 0;
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
}
public function testSuiteFinished(): void
{
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'tests',
(string) $this->testSuiteTests[$this->testSuiteLevel],
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'assertions',
(string) $this->testSuiteAssertions[$this->testSuiteLevel],
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'errors',
(string) $this->testSuiteErrors[$this->testSuiteLevel],
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'failures',
(string) $this->testSuiteFailures[$this->testSuiteLevel],
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'skipped',
(string) $this->testSuiteSkipped[$this->testSuiteLevel],
);
$this->testSuites[$this->testSuiteLevel]->setAttribute(
'time',
sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]),
);
if ($this->testSuiteLevel > 1) {
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
}
$this->testSuiteLevel--;
}
/**
* @throws InvalidArgumentException
*/
public function testPreparationStarted(PreparationStarted $event): void
{
$this->createTestCase($event);
}
public function testPreparationFailed(): void
{
$this->preparationFailed = true;
}
public function testPrepared(): void
{
$this->prepared = true;
}
public function testPrintedUnexpectedOutput(PrintedUnexpectedOutput $event): void
{
assert($this->currentTestCase !== null);
$systemOut = $this->document->createElement(
'system-out',
Xml::prepareString($event->output()),
);
$this->currentTestCase->appendChild($systemOut);
}
/**
* @throws InvalidArgumentException
*/
public function testFinished(Finished $event): void
{
if (! $this->prepared || $this->preparationFailed) {
return;
}
$this->handleFinish($event->telemetryInfo(), $event->numberOfAssertionsPerformed());
}
/**
* @throws InvalidArgumentException
*/
public function testMarkedIncomplete(MarkedIncomplete $event): void
{
$this->handleIncompleteOrSkipped($event);
}
/**
* @throws InvalidArgumentException
*/
public function testSkipped(Skipped $event): void
{
$this->handleIncompleteOrSkipped($event);
}
/**
* @throws InvalidArgumentException
*/
public function testErrored(Errored $event): void
{
$this->handleFault($event, 'error');
$this->testSuiteErrors[$this->testSuiteLevel]++;
}
/**
* @throws InvalidArgumentException
*/
public function testFailed(Failed $event): void
{
$this->handleFault($event, 'failure');
$this->testSuiteFailures[$this->testSuiteLevel]++;
}
/**
* @throws InvalidArgumentException
*/
private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerformed): void
{
assert($this->currentTestCase !== null);
assert($this->time !== null);
$time = $telemetryInfo->time()->duration($this->time)->asFloat();
$this->testSuiteAssertions[$this->testSuiteLevel] += $numberOfAssertionsPerformed;
$this->currentTestCase->setAttribute(
'assertions',
(string) $numberOfAssertionsPerformed,
);
$this->currentTestCase->setAttribute(
'time',
sprintf('%F', $time),
);
$this->testSuites[$this->testSuiteLevel]->appendChild(
$this->currentTestCase,
);
$this->testSuiteTests[$this->testSuiteLevel]++;
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
$this->currentTestCase = null;
$this->time = null;
$this->prepared = false;
}
/**
* @throws EventFacadeIsSealedException
* @throws UnknownSubscriberTypeException
*/
private function registerSubscribers(Facade $facade): void
{
$facade->registerSubscribers(
new TestSuiteStartedSubscriber($this),
new TestSuiteFinishedSubscriber($this),
new TestPreparationStartedSubscriber($this),
new TestPreparationFailedSubscriber($this),
new TestPreparedSubscriber($this),
new TestPrintedUnexpectedOutputSubscriber($this),
new TestFinishedSubscriber($this),
new TestErroredSubscriber($this),
new TestFailedSubscriber($this),
new TestMarkedIncompleteSubscriber($this),
new TestSkippedSubscriber($this),
new TestRunnerExecutionFinishedSubscriber($this),
);
}
private function createDocument(): void
{
$this->document = new DOMDocument('1.0', 'UTF-8');
$this->document->formatOutput = true;
$this->root = $this->document->createElement('testsuites');
$this->document->appendChild($this->root);
}
/**
* @throws InvalidArgumentException
*/
private function handleFault(Errored|Failed $event, string $type): void
{
if (! $this->prepared) {
$this->createTestCase($event);
}
assert($this->currentTestCase !== null);
$buffer = $this->converter->getTestCaseMethodName($event->test()); // pest-changed
$throwable = $event->throwable();
$buffer .= trim(
$this->converter->getExceptionMessage($throwable).PHP_EOL. // pest-changed
$this->converter->getExceptionDetails($throwable), // pest-changed
);
$fault = $this->document->createElement(
$type,
Xml::prepareString($buffer),
);
$fault->setAttribute('type', $throwable->className());
$this->currentTestCase->appendChild($fault);
if (! $this->prepared) {
$this->handleFinish($event->telemetryInfo(), 0);
}
}
/**
* @throws InvalidArgumentException
*/
private function handleIncompleteOrSkipped(MarkedIncomplete|Skipped $event): void
{
if (! $this->prepared) {
$this->createTestCase($event);
}
assert($this->currentTestCase !== null);
$skipped = $this->document->createElement('skipped');
$this->currentTestCase->appendChild($skipped);
$this->testSuiteSkipped[$this->testSuiteLevel]++;
if (! $this->prepared) {
$this->handleFinish($event->telemetryInfo(), 0);
}
}
/**
* @throws InvalidArgumentException
*/
private function testAsString(Test $test): string
{
if ($test->isPhpt()) {
return basename($test->file());
}
assert($test instanceof TestMethod);
return sprintf(
'%s::%s%s',
$test->className(),
$this->name($test),
PHP_EOL,
);
}
/**
* @throws InvalidArgumentException
*/
private function name(Test $test): string
{
if ($test->isPhpt()) {
return basename($test->file());
}
assert($test instanceof TestMethod);
if (! $test->testData()->hasDataFromDataProvider()) {
return $test->methodName();
}
$dataSetName = $test->testData()->dataFromDataProvider()->dataSetName();
if (is_int($dataSetName)) {
return sprintf(
'%s with data set #%d',
$test->methodName(),
$dataSetName,
);
}
return sprintf(
'%s with data set "%s"',
$test->methodName(),
$dataSetName,
);
}
/**
* @throws InvalidArgumentException
*
* @phpstan-assert !null $this->currentTestCase
*/
private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void
{
$testCase = $this->document->createElement('testcase');
$test = $event->test();
$file = $this->converter->getTestCaseLocation($test); // pest-added
$testCase->setAttribute('name', $this->converter->getTestCaseMethodName($test)); // pest-changed
$testCase->setAttribute('file', $file); // pest-changed
if ($test->isTestMethod()) {
assert($test instanceof TestMethod);
// $testCase->setAttribute('line', (string) $test->line()); // pest-removed
$className = $this->converter->getTrimmedTestClassName($test); // pest-added
$testCase->setAttribute('class', $className); // pest-changed
$testCase->setAttribute('classname', str_replace('\\', '.', $className)); // pest-changed
}
$this->currentTestCase = $testCase;
$this->time = $event->telemetryInfo()->time();
}
}
+63 -54
View File
@@ -32,41 +32,58 @@
* 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\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 PHPUnit\Runner\PhptTestCase;
use RecursiveFilterIterator;
use RecursiveIterator;
use function end;
use function preg_match;
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
abstract class NameFilterIterator extends RecursiveFilterIterator
{
private ?string $filter = null;
/**
* @psalm-var non-empty-string
*/
private readonly string $regularExpression;
private ?int $filterMin = null;
private readonly ?int $dataSetMinimum;
private ?int $filterMax = null;
private readonly ?int $dataSetMaximum;
/**
* @throws Exception
* @psalm-param RecursiveIterator<int, Test> $iterator
* @psalm-param non-empty-string $filter
*/
public function __construct(RecursiveIterator $iterator, string $filter)
{
parent::__construct($iterator);
$this->setFilter($filter);
$preparedFilter = $this->prepareFilter($filter);
$this->regularExpression = $preparedFilter['regularExpression'];
$this->dataSetMinimum = $preparedFilter['dataSetMinimum'];
$this->dataSetMaximum = $preparedFilter['dataSetMaximum'];
}
public function accept(): bool
@@ -77,29 +94,40 @@ final class NameFilterIterator extends RecursiveFilterIterator
return true;
}
$tmp = $this->describe($test);
if ($test instanceof PhptTestCase) {
return false;
}
if ($tmp[0] !== '') {
$name = implode('::', $tmp);
if ($test instanceof HasPrintableTestCaseName) {
$name = trim(
$test::getPrintableTestCaseName().'::'.$test->getPrintableTestCaseMethodName().$test->dataSetAsString()
);
} else {
$name = $tmp[1];
$name = $test::class.'::'.$test->nameWithDataSet();
}
$accepted = @preg_match($this->filter, $name, $matches);
$accepted = @preg_match($this->regularExpression, $name, $matches) === 1;
if ($accepted && isset($this->filterMax)) {
if ($accepted && isset($this->dataSetMaximum)) {
$set = end($matches);
$accepted = $set >= $this->filterMin && $set <= $this->filterMax;
$accepted = $set >= $this->dataSetMinimum && $set <= $this->dataSetMaximum;
}
return (bool) $accepted;
return $this->doAccept($accepted);
}
abstract protected function doAccept(bool $result): bool;
/**
* @throws Exception
* @psalm-param non-empty-string $filter
*
* @psalm-return array{regularExpression: non-empty-string, dataSetMinimum: ?int, dataSetMaximum: ?int}
*/
private function setFilter(string $filter): void
private function prepareFilter(string $filter): array
{
$dataSetMinimum = null;
$dataSetMaximum = null;
if (@preg_match($filter, '') === false) {
// Handles:
// * testAssertEqualsSucceeds#4
@@ -107,17 +135,17 @@ final class NameFilterIterator extends RecursiveFilterIterator
if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
if (isset($matches[3]) && $matches[2] < $matches[3]) {
$filter = sprintf(
'%s.*with dataset #(\d+)$',
$matches[1]
'%s.*with data set #(\d+)$',
$matches[1],
);
$this->filterMin = (int) $matches[2];
$this->filterMax = (int) $matches[3];
$dataSetMinimum = (int) $matches[2];
$dataSetMaximum = (int) $matches[3];
} else {
$filter = sprintf(
'%s.*with dataset #%s$',
'%s.*with data set #%s$',
$matches[1],
$matches[2]
$matches[2],
);
}
} // Handles:
@@ -125,9 +153,9 @@ final class NameFilterIterator extends RecursiveFilterIterator
// * testDetermineJsonError@JSON.*
elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
$filter = sprintf(
'%s.*with dataset "%s"$',
'%s.*with data set "%s"$',
$matches[1],
$matches[2]
$matches[2],
);
}
@@ -138,34 +166,15 @@ final class NameFilterIterator extends RecursiveFilterIterator
str_replace(
'/',
'\\/',
$filter
)
$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(),
'regularExpression' => $filter,
'dataSetMinimum' => $dataSetMinimum,
'dataSetMaximum' => $dataSetMaximum,
];
}
if ($test instanceof TestCase) {
return [$test::class, $test->nameWithDataSet()];
}
if ($test instanceof SelfDescribing) {
return ['', $test->toString()];
}
return ['', $test::class];
}
}
@@ -45,9 +45,16 @@ declare(strict_types=1);
namespace PHPUnit\Runner\ResultCache;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use PHPUnit\Framework\TestStatus\TestStatus;
use PHPUnit\Runner\DirectoryDoesNotExistException;
use PHPUnit\Runner\Exception;
use PHPUnit\Util\Filesystem;
use function array_keys;
use function assert;
use const DIRECTORY_SEPARATOR;
use function dirname;
use function file_get_contents;
use function file_put_contents;
@@ -57,39 +64,29 @@ 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;
/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @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 const string DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache';
private readonly string $cacheFilename;
/**
* @psalm-var array<string, TestStatus>
* @var array<string, TestStatus>
*/
private array $defects = [];
/**
* @psalm-var array<string, TestStatus>
*/
private array $currentDefects = [];
/**
* @psalm-var array<string, float>
* @var array<string, float>
*/
private array $times = [];
public function __construct(string $filepath = null)
public function __construct(?string $filepath = null)
{
if ($filepath !== null && is_dir($filepath)) {
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_RESULT_CACHE_FILENAME;
@@ -98,31 +95,39 @@ final class DefaultResultCache implements ResultCache
$this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME;
}
public function setStatus(string $id, TestStatus $status): void
public function setStatus(ResultCacheId $id, TestStatus $status): void
{
if ($status->isFailure() || $status->isError()) {
$this->currentDefects[$id] = $status;
$this->defects[$id] = $status;
}
if ($status->isSuccess()) {
return;
}
public function status(string $id): TestStatus
{
return $this->defects[$id] ?? TestStatus::unknown();
$this->defects[$id->asString()] = $status;
}
public function setTime(string $id, float $time): void
public function status(ResultCacheId $id): TestStatus
{
if (! isset($this->currentDefects[$id])) {
unset($this->defects[$id]);
return $this->defects[$id->asString()] ?? TestStatus::unknown();
}
public function setTime(ResultCacheId $id, float $time): void
{
$this->times[$id->asString()] = $time;
}
public function time(ResultCacheId $id): float
{
return $this->times[$id->asString()] ?? 0.0;
}
public function mergeWith(self $other): void
{
foreach ($other->defects as $id => $defect) {
$this->defects[$id] = $defect;
}
foreach ($other->times as $id => $time) {
$this->times[$id] = $time;
}
public function time(string $id): float
{
return $this->times[$id] ?? 0.0;
}
public function load(): void
@@ -131,9 +136,15 @@ final class DefaultResultCache implements ResultCache
return;
}
$contents = file_get_contents($this->cacheFilename);
if ($contents === false) {
return;
}
$data = json_decode(
file_get_contents($this->cacheFilename),
true
$contents,
true,
);
if ($data === null) {
@@ -165,7 +176,7 @@ final class DefaultResultCache implements ResultCache
public function persist(): void
{
if (! Filesystem::createDirectory(dirname($this->cacheFilename))) {
throw new DirectoryCannotBeCreatedException($this->cacheFilename);
throw new DirectoryDoesNotExistException(dirname($this->cacheFilename));
}
$data = [
@@ -181,7 +192,7 @@ final class DefaultResultCache implements ResultCache
file_put_contents(
$this->cacheFilename,
json_encode($data),
LOCK_EX
LOCK_EX,
);
}
+29 -6
View File
@@ -36,18 +36,21 @@ 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\Panic;
use Pest\TestCases\IgnorableTestCase;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionException;
use Throwable;
use function array_diff;
use function array_values;
use function basename;
use function class_exists;
use function get_declared_classes;
use function substr;
/**
@@ -60,6 +63,11 @@ final class TestSuiteLoader
*/
private static array $loadedClasses = [];
/**
* @psalm-var array<string, array<class-string>>
*/
private static array $loadedClassesByFilename = [];
/**
* @psalm-var list<class-string>
*/
@@ -80,7 +88,11 @@ final class TestSuiteLoader
$suiteClassName = $this->classNameFromFileName($suiteClassFile);
(static function () use ($suiteClassFile) {
try {
include_once $suiteClassFile;
} catch (Throwable $e) {
Panic::with($e);
}
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
})();
@@ -97,6 +109,17 @@ final class TestSuiteLoader
self::$loadedClasses = array_merge($loadedClasses, self::$loadedClasses);
foreach ($loadedClasses as $loadedClass) {
$reflection = new ReflectionClass($loadedClass);
$filename = $reflection->getFileName();
self::$loadedClassesByFilename[$filename] = [
$loadedClass,
...self::$loadedClassesByFilename[$filename] ?? [],
];
}
$loadedClasses = array_merge(self::$loadedClassesByFilename[$suiteClassFile] ?? [], $loadedClasses);
if (empty($loadedClasses)) {
return $this->exceptionFor($suiteClassName, $suiteClassFile);
}
@@ -115,7 +138,7 @@ final class TestSuiteLoader
continue;
}
if ($class->isAbstract() || ($class->getFileName() !== $suiteClassFile)) {
if ($class->isAbstract() || ($suiteClassFile !== $class->getFileName())) {
if (! str_contains($class->getFileName(), 'TestCaseFactory.php')) {
continue;
}
+388
View File
@@ -0,0 +1,388 @@
<?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;
use PHPUnit\Framework\DataProviderTestSuite;
use PHPUnit\Framework\Reorderable;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\ResultCache\NullResultCache;
use PHPUnit\Runner\ResultCache\ResultCache;
use PHPUnit\Runner\ResultCache\ResultCacheId;
use function array_diff;
use function array_merge;
use function array_reverse;
use function array_splice;
use function assert;
use function count;
use function in_array;
use function max;
use function shuffle;
use function usort;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class TestSuiteSorter
{
public const int ORDER_DEFAULT = 0;
public const int ORDER_RANDOMIZED = 1;
public const int ORDER_REVERSED = 2;
public const int ORDER_DEFECTS_FIRST = 3;
public const int ORDER_DURATION = 4;
public const int ORDER_SIZE = 5;
/**
* @var non-empty-array<non-empty-string, positive-int>
*/
private const array SIZE_SORT_WEIGHT = [
'small' => 1,
'medium' => 2,
'large' => 3,
'unknown' => 4,
];
/**
* @var array<string, int> Associative array of (string => DEFECT_SORT_WEIGHT) elements
*/
private array $defectSortOrder = [];
private readonly ResultCache $cache;
public function __construct(?ResultCache $cache = null)
{
$this->cache = $cache ?? new NullResultCache;
}
/**
* @throws Exception
*/
public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects): void
{
$allowedOrders = [
self::ORDER_DEFAULT,
self::ORDER_REVERSED,
self::ORDER_RANDOMIZED,
self::ORDER_DURATION,
self::ORDER_SIZE,
];
if (! in_array($order, $allowedOrders, true)) {
// @codeCoverageIgnoreStart
throw new InvalidOrderException;
// @codeCoverageIgnoreEnd
}
$allowedOrderDefects = [
self::ORDER_DEFAULT,
self::ORDER_DEFECTS_FIRST,
];
if (! in_array($orderDefects, $allowedOrderDefects, true)) {
// @codeCoverageIgnoreStart
throw new InvalidOrderException;
// @codeCoverageIgnoreEnd
}
if ($suite instanceof TestSuite) {
foreach ($suite as $_suite) {
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects);
}
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
$this->addSuiteToDefectSortOrder($suite);
}
$this->sort($suite, $order, $resolveDependencies, $orderDefects);
}
}
private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void
{
if ($suite->tests() === []) {
return;
}
if ($order === self::ORDER_REVERSED) {
$suite->setTests($this->reverse($suite->tests()));
} elseif ($order === self::ORDER_RANDOMIZED) {
$suite->setTests($this->randomize($suite->tests()));
} elseif ($order === self::ORDER_DURATION) {
$suite->setTests($this->sortByDuration($suite->tests()));
} elseif ($order === self::ORDER_SIZE) {
$suite->setTests($this->sortBySize($suite->tests()));
}
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
$suite->setTests($this->sortDefectsFirst($suite->tests()));
}
if ($resolveDependencies && ! ($suite instanceof DataProviderTestSuite)) {
$tests = $suite->tests();
/** @noinspection PhpParamsInspection */
/** @phpstan-ignore argument.type */
$suite->setTests($this->resolveDependencies($tests));
}
}
private function addSuiteToDefectSortOrder(TestSuite $suite): void
{
$max = 0;
foreach ($suite->tests() as $test) {
assert($test instanceof Reorderable);
$sortId = $test->sortId();
if (! isset($this->defectSortOrder[$sortId])) {
$this->defectSortOrder[$sortId] = $this->cache->status(ResultCacheId::fromReorderable($test))->asInt();
$max = max($max, $this->defectSortOrder[$sortId]);
}
}
$this->defectSortOrder[$suite->sortId()] = $max;
}
/**
* @param list<Test> $tests
* @return list<Test>
*/
private function reverse(array $tests): array
{
return array_reverse($tests);
}
/**
* @param list<Test> $tests
* @return list<Test>
*/
private function randomize(array $tests): array
{
shuffle($tests);
return $tests;
}
/**
* @param list<Test> $tests
* @return list<Test>
*/
private function sortDefectsFirst(array $tests): array
{
usort(
$tests,
fn (Test $left, Test $right) => $this->cmpDefectPriorityAndTime($left, $right),
);
return $tests;
}
/**
* @param list<Test> $tests
* @return list<Test>
*/
private function sortByDuration(array $tests): array
{
usort(
$tests,
fn (Test $left, Test $right) => $this->cmpDuration($left, $right),
);
return $tests;
}
/**
* @param list<Test> $tests
* @return list<Test>
*/
private function sortBySize(array $tests): array
{
usort(
$tests,
fn (Test $left, Test $right) => $this->cmpSize($left, $right),
);
return $tests;
}
/**
* Comparator callback function to sort tests for "reach failure as fast as possible".
*
* 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT
* 2. when tests are equally defective, sort the fastest to the front
* 3. do not reorder successful tests
*/
private function cmpDefectPriorityAndTime(Test $a, Test $b): int
{
assert($a instanceof Reorderable);
assert($b instanceof Reorderable);
$priorityA = $this->defectSortOrder[$a->sortId()] ?? 0;
$priorityB = $this->defectSortOrder[$b->sortId()] ?? 0;
if ($priorityA !== $priorityB) {
// Sort defect weight descending
return $priorityB <=> $priorityA;
}
if ($priorityA > 0 || $priorityB > 0) {
return $this->cmpDuration($a, $b);
}
// do not change execution order
return 0;
}
/**
* Compares test duration for sorting tests by duration ascending.
*/
private function cmpDuration(Test $a, Test $b): int
{
if (! ($a instanceof Reorderable && $b instanceof Reorderable)) {
return 0;
}
return $this->cache->time(ResultCacheId::fromReorderable($a)) <=> $this->cache->time(ResultCacheId::fromReorderable($b));
}
/**
* Compares test size for sorting tests small->medium->large->unknown.
*/
private function cmpSize(Test $a, Test $b): int
{
$sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite)
? $a->size()->asString()
: 'unknown';
$sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite)
? $b->size()->asString()
: 'unknown';
return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB];
}
/**
* Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible.
* The algorithm will leave the tests in original running order when it can.
* For more details see the documentation for test dependencies.
*
* Short description of algorithm:
* 1. Pick the next Test from remaining tests to be checked for dependencies.
* 2. If the test has no dependencies: mark done, start again from the top
* 3. If the test has dependencies but none left to do: mark done, start again from the top
* 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution.
*
* @param array<TestCase> $tests
* @return array<TestCase>
*/
private function resolveDependencies(array $tests): array
{
// Pest: Fast-path. If no test in this suite declares dependencies, the
// original O(N^2) algorithm is wasted work — it would splice each test
// one-by-one back into the same order. The check deliberately walks
// TestCase instances directly instead of calling TestSuite::requires(),
// because the latter lazily builds TestSuite::provides() via
// ExecutionOrderDependency::mergeUnique, which is O(N^2) in the total
// number of tests. With thousands of tests that single call alone can
// burn several seconds before the sort even begins. Reading the
// cached TestCase::$dependencies property stays O(N) and costs nothing
// when no test uses `->depends()` / PHPUnit `@depends`.
if (! $this->anyTestHasDependencies($tests)) {
return $tests;
}
$newTestOrder = [];
$i = 0;
$provided = [];
do {
if (array_diff($tests[$i]->requires(), $provided) === []) {
$provided = array_merge($provided, $tests[$i]->provides());
$newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1));
$i = 0;
} else {
$i++;
}
} while ($tests !== [] && ($i < count($tests)));
return array_merge($newTestOrder, $tests);
}
/**
* Cheaply determines whether any test in the tree declares @depends.
*
* Walks `TestSuite` containers recursively and inspects each `TestCase`
* directly so it never triggers `TestSuite::provides()`, which is O(N^2)
* in the total number of aggregated tests.
*
* @param iterable<Test> $tests
*/
private function anyTestHasDependencies(iterable $tests): bool
{
foreach ($tests as $test) {
if ($test instanceof TestSuite) {
if ($this->anyTestHasDependencies($test->tests())) {
return true;
}
continue;
}
if ($test instanceof TestCase && $test->requires() !== []) {
return true;
}
}
return false;
}
}
@@ -45,6 +45,8 @@ declare(strict_types=1);
namespace PHPUnit\TextUI\Command;
use const PHP_EOL;
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
use PHPUnit\TextUI\Configuration\Configuration;
use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException;
@@ -55,11 +57,11 @@ use SebastianBergmann\Timer\Timer;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class WarmCodeCoverageCacheCommand implements Command
final readonly class WarmCodeCoverageCacheCommand implements Command
{
private readonly Configuration $configuration;
private Configuration $configuration;
private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
private CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry)
{
@@ -76,16 +78,16 @@ final class WarmCodeCoverageCacheCommand implements Command
if (! $this->configuration->hasCoverageCacheDirectory()) {
return Result::from(
'Cache for static analysis has not been configured'.PHP_EOL,
Result::FAILURE
Result::FAILURE,
);
}
$this->codeCoverageFilterRegistry->init($this->configuration);
$this->codeCoverageFilterRegistry->init($this->configuration, true);
if (! $this->codeCoverageFilterRegistry->configured()) {
return Result::from(
'Filter for code coverage has not been configured'.PHP_EOL,
Result::FAILURE
Result::FAILURE,
);
}
@@ -96,7 +98,7 @@ final class WarmCodeCoverageCacheCommand implements Command
$this->configuration->coverageCacheDirectory(),
! $this->configuration->disableCodeCoverageIgnore(),
$this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(),
$this->codeCoverageFilterRegistry->get()
$this->codeCoverageFilterRegistry->get(),
);
return Result::from();
@@ -43,7 +43,7 @@ declare(strict_types=1);
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI\Output\Default\ProgressPrinter;
namespace Pest\Logging\TeamCity\Subscriber;
use PHPUnit\Event\Test\Skipped;
use PHPUnit\Event\Test\SkippedSubscriber;
@@ -51,21 +51,16 @@ 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();
$this->logger()->testSkipped($event);
}
/**
+37 -27
View File
@@ -33,7 +33,6 @@
*/
declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
@@ -45,84 +44,95 @@ declare(strict_types=1);
namespace PHPUnit\TextUI;
use function array_map;
use Pest\Plugins\Only;
use Pest\Runner\Filter\EnsureTestCaseIsInitiatedFilter;
use PHPUnit\Event;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Runner\Filter\Factory;
use PHPUnit\TextUI\Configuration\Configuration;
use PHPUnit\TextUI\Configuration\FilterNotConfiguredException;
use function array_map;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class TestSuiteFilterProcessor
final readonly 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
{
$factory = new Factory;
// @phpstan-ignore-next-line
(fn () => $this->filters[] = [
'className' => EnsureTestCaseIsInitiatedFilter::class,
'argument' => '',
])->call($factory);
if (! $configuration->hasFilter() &&
! $configuration->hasGroups() &&
! $configuration->hasExcludeGroups() &&
! $configuration->hasExcludeFilter() &&
! $configuration->hasTestsCovering() &&
! $configuration->hasTestsUsing() &&
! Only::isEnabled()
) {
! Only::isEnabled()) {
$suite->injectFilter($factory);
return;
}
if ($configuration->hasExcludeGroups()) {
$this->filterFactory->addExcludeGroupFilter(
$configuration->excludeGroups()
$factory->addExcludeGroupFilter(
$configuration->excludeGroups(),
);
}
if (Only::isEnabled()) {
$this->filterFactory->addIncludeGroupFilter(['__pest_only']);
$factory->addIncludeGroupFilter([Only::group()]);
} elseif ($configuration->hasGroups()) {
$this->filterFactory->addIncludeGroupFilter(
$configuration->groups()
$factory->addIncludeGroupFilter(
$configuration->groups(),
);
}
if ($configuration->hasTestsCovering()) {
$this->filterFactory->addIncludeGroupFilter(
$factory->addIncludeGroupFilter(
array_map(
static fn (string $name): string => '__phpunit_covers_'.$name,
$configuration->testsCovering()
)
$configuration->testsCovering(),
),
);
}
if ($configuration->hasTestsUsing()) {
$this->filterFactory->addIncludeGroupFilter(
$factory->addIncludeGroupFilter(
array_map(
static fn (string $name): string => '__phpunit_uses_'.$name,
$configuration->testsUsing()
)
$configuration->testsUsing(),
),
);
}
if ($configuration->hasExcludeFilter()) {
$factory->addExcludeNameFilter(
$configuration->excludeFilter(),
);
}
if ($configuration->hasFilter()) {
$this->filterFactory->addNameFilter(
$configuration->filter()
$factory->addIncludeNameFilter(
$configuration->filter(),
);
}
$suite->injectFilter($this->filterFactory);
$suite->injectFilter($factory);
Event\Facade::emitter()->testSuiteFiltered(
Event\TestSuite\TestSuiteBuilder::from($suite)
Event\TestSuite\TestSuiteBuilder::from($suite),
);
}
}
+121
View File
@@ -0,0 +1,121 @@
parameters:
ignoreErrors:
-
message: '#^Trait Pest\\Concerns\\Expectable is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: src/Concerns/Expectable.php
-
message: '#^Trait Pest\\Concerns\\Logging\\WritesToConsole is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: src/Concerns/Logging/WritesToConsole.php
-
message: '#^Trait Pest\\Concerns\\Testable is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: src/Concerns/Testable.php
-
message: '#^Method Pest\\Expectation\:\:and\(\) should return Pest\\Expectation\<TAndValue\> but returns \(Pest\\Expectation&TAndValue\)\|Pest\\Expectation\<TAndValue of mixed\>\.$#'
identifier: return.type
count: 1
path: src/Expectation.php
-
message: '#^PHPDoc tag @property for property Pest\\Expectation\:\:\$each contains generic class Pest\\Expectations\\EachExpectation but does not specify its types\: TValue$#'
identifier: missingType.generics
count: 1
path: src/Expectation.php
-
message: '#^PHPDoc tag @property for property Pest\\Expectation\:\:\$not contains generic class Pest\\Expectations\\OppositeExpectation but does not specify its types\: TValue$#'
identifier: missingType.generics
count: 1
path: src/Expectation.php
-
message: '#^Parameter \#2 \$newScope of method Closure\:\:bindTo\(\) expects ''static''\|class\-string\|object\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/Expectation.php
-
message: '#^Function expect\(\) should return Pest\\Expectation\<TValue\|null\> but returns Pest\\Expectation\<TValue\|null\>\.$#'
identifier: return.type
count: 1
path: src/Functions.php
-
message: '#^Parameter \#1 \$argv of method PHPUnit\\TextUI\\Application\:\:run\(\) expects list\<string\>, array\<int, string\> given\.$#'
identifier: argument.type
count: 1
path: src/Kernel.php
-
message: '#^Call to an undefined method object&TValue of mixed\:\:__toString\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mixins/Expectation.php
-
message: '#^Call to an undefined method object&TValue of mixed\:\:toArray\(\)\.$#'
identifier: method.notFound
count: 4
path: src/Mixins/Expectation.php
-
message: '#^Call to an undefined method object&TValue of mixed\:\:toSnapshot\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mixins/Expectation.php
-
message: '#^Call to an undefined method object&TValue of mixed\:\:toString\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mixins/Expectation.php
-
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with true will always evaluate to true\.$#'
identifier: staticMethod.alreadyNarrowedType
count: 2
path: src/Mixins/Expectation.php
-
message: '#^PHPDoc tag @var with type callable\(\)\: bool is not subtype of native type Closure\|null\.$#'
identifier: varTag.nativeType
count: 1
path: src/PendingCalls/TestCall.php
-
message: '#^Parameter \#4 \$testErroredEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\Test\\AfterLastTestMethodErrored\|PHPUnit\\Event\\Test\\BeforeFirstTestMethodErrored\|PHPUnit\\Event\\Test\\Errored\>, array given\.$#'
identifier: argument.type
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
-
message: '#^Parameter \#7 \$testSuiteSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\TestSuite\\Skipped\>, array given\.$#'
identifier: argument.type
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
-
message: '#^Parameter \#8 \$testSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\Test\\Skipped\>, array given\.$#'
identifier: argument.type
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
-
message: '#^Parameter \#9 \$testMarkedIncompleteEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\Test\\MarkedIncomplete\>, array given\.$#'
identifier: argument.type
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
-
message: '#^Property Pest\\Plugins\\Parallel\\Paratest\\WrapperRunner\:\:\$pending \(list\<non\-empty\-string\>\) does not accept array\<int, non\-empty\-string\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
+5
View File
@@ -0,0 +1,5 @@
services:
-
class: Pest\PHPStan\HigherOrderExpectationTypeExtension
tags:
- phpstan.broker.expressionTypeResolverExtension
+5 -17
View File
@@ -1,23 +1,11 @@
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/ergebnis/phpstan-rules/rules.neon
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
- phpstan-baseline.neon
- phpstan-pest-extension.neon
- vendor/mrpunyapal/peststan/extension.neon
parameters:
level: max
level: 7
paths:
- src
checkMissingIterableValueType: true
reportUnmatchedIgnoredErrors: true
ignoreErrors:
- "#has a nullable return type declaration.#"
- "#Language construct isset\\(\\) should not be used.#"
- "#is not allowed to extend#"
- "#is concrete, but does not have a Test suffix#"
- "#with a nullable type declaration#"
- "#type mixed is not subtype of native#"
- "# with null as default value#"
- "#has parameter \\$closure with default value.#"
- "#has parameter \\$description with default value.#"
reportUnmatchedIgnoredErrors: false
+1
View File
@@ -16,6 +16,7 @@
<testsuites>
<testsuite name="default">
<directory suffix=".php">./tests</directory>
<directory suffix=".php">./tests-external</directory>
<exclude>./tests/.snapshots</exclude>
<exclude>./tests/.tests</exclude>
<exclude>./tests/Fixtures/Inheritance</exclude>
+25 -23
View File
@@ -2,30 +2,32 @@
declare(strict_types=1);
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\CodingStyle\Rector\ArrowFunction\ArrowFunctionDelegatingCallToFirstClassCallableRector;
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
use Rector\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
return RectorConfig::configure()
->withPaths([
__DIR__.'/src',
]);
$rectorConfig->rules([
InlineConstructorDefaultToPropertyRector::class,
]);
$rectorConfig->skip([
])
->withSkip([
__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,
]);
};
ReturnNeverTypeRector::class,
ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
NarrowObjectReturnTypeRector::class,
RemoveParentDelegatingConstructorRector::class,
RemoveDuplicatedReturnSelfDocblockRector::class,
RemoveUselessUnionReturnDocblockRector::class,
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
privatization: true,
earlyReturn: true,
)
->withPhpSets();
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Default">
<directory>tests/</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
<directory>src</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
+3 -1
View File
@@ -5,12 +5,14 @@
[$bgBadgeColor, $bgBadgeText] = match ($type) {
'INFO' => ['blue', 'INFO'],
'ERROR' => ['red', 'ERROR'],
'WARN' => ['yellow', 'WARN'],
'SUCCESS' => ['green', 'SUCCESS'],
};
?>
<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-2 px-1 bg-<?php echo $bgBadgeColor ?> font-bold"><?php echo htmlspecialchars($bgBadgeText) ?></span>
<span class="ml-1">
<?php echo htmlspecialchars($content) ?>
</span>
@@ -0,0 +1,22 @@
<div class="mx-2 mb-1">
<p>
<span>Using the <span class="text-yellow font-bold">visit()</span> function requires the Pest Plugin Browser to be installed.</span>
<span class="ml-1 text-yellow font-bold">Run:</span>
</p>
<div>
<span class="text-gray mr-1">- </span>
<span>composer require pestphp/pest-plugin-browser:^5.0 --dev</span>
</div>
<div>
<span class="text-gray mr-1">- </span>
<span>npm install playwright@latest</span>
</div>
<div>
<span class="text-gray mr-1">- </span>
<span>npx playwright install</span>
</div>
</div>
+74
View File
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Expectation;
/**
* @internal
*/
abstract class AbstractPreset // @pest-arch-ignore-line
{
/**
* The expectations.
*
* @var array<int, Expectation<mixed>|ArchExpectation>
*/
protected array $expectations = [];
/**
* Creates a new preset instance.
*
* @param array<int, string> $userNamespaces
*/
public function __construct(
private readonly array $userNamespaces,
) {
//
}
/**
* Executes the arch preset.
*
* @internal
*/
abstract public function execute(): void;
/**
* Ignores the given "targets" or "dependencies".
*
* @param array<int, string>|string $targetsOrDependencies
*/
final public function ignoring(array|string $targetsOrDependencies): void
{
$this->expectations = array_map(
fn (ArchExpectation|Expectation $expectation): Expectation|ArchExpectation => $expectation instanceof ArchExpectation ? $expectation->ignoring($targetsOrDependencies) : $expectation,
$this->expectations,
);
}
/**
* Runs the given callback for each namespace.
*
* @param callable(Expectation<string>): ArchExpectation ...$callbacks
*/
final public function eachUserNamespace(callable ...$callbacks): void
{
foreach ($this->userNamespaces as $namespace) {
foreach ($callbacks as $callback) {
$this->expectations[] = $callback(expect($namespace));
}
}
}
/**
* Flushes the expectations.
*/
final public function flush(): void
{
$this->expectations = [];
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
use Closure;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Expectation;
/**
* @internal
*/
final class Custom extends AbstractPreset
{
/**
* Creates a new preset instance.
*
* @param array<int, string> $userNamespaces
* @param Closure(array<int, string>): array<Expectation<mixed>|ArchExpectation> $execute
*/
public function __construct(
private readonly array $userNamespaces,
private readonly string $name,
private readonly Closure $execute,
) {
parent::__construct($userNamespaces);
}
/**
* Returns the name of the preset.
*/
public function name(): string
{
return $this->name;
}
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations = ($this->execute)($this->userNamespaces);
}
}
+180
View File
@@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
use Throwable;
/**
* @internal
*/
final class Laravel extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations[] = expect('App\Traits')
->toBeTraits();
$this->expectations[] = expect('App\Concerns')
->toBeTraits();
$this->expectations[] = expect('App')
->not->toBeEnums()
->ignoring('App\Enums');
$this->expectations[] = expect('App\Enums')
->toBeEnums()
->ignoring('App\Enums\Concerns');
$this->expectations[] = expect('App\Features')
->toBeClasses()
->ignoring('App\Features\Concerns');
$this->expectations[] = expect('App\Features')
->toHaveMethod('resolve')
->ignoring('App\Features\Concerns');
$this->expectations[] = expect('App\Exceptions')
->classes()
->toImplement('Throwable')
->ignoring('App\Exceptions\Handler');
$this->expectations[] = expect('App')
->not->toImplement(Throwable::class)
->ignoring('App\Exceptions');
$this->expectations[] = expect('App\Http\Middleware')
->classes()
->toHaveMethod('handle');
$this->expectations[] = expect('App\Models')
->classes()
->toExtend('Illuminate\Database\Eloquent\Model')
->ignoring('App\Models\Scopes');
$this->expectations[] = expect('App\Models')
->classes()
->not->toHaveSuffix('Model');
$this->expectations[] = expect('App')
->not->toExtend('Illuminate\Database\Eloquent\Model')
->ignoring('App\Models');
$this->expectations[] = expect('App\Http\Requests')
->classes()
->toHaveSuffix('Request');
$this->expectations[] = expect('App\Http\Requests')
->classes()
->toExtend('Illuminate\Foundation\Http\FormRequest');
$this->expectations[] = expect('App\Http\Requests')
->toHaveMethod('rules');
$this->expectations[] = expect('App')
->not->toExtend('Illuminate\Foundation\Http\FormRequest')
->ignoring('App\Http\Requests');
$this->expectations[] = expect('App\Console\Commands')
->classes()
->toHaveSuffix('Command');
$this->expectations[] = expect('App\Console\Commands')
->classes()
->toExtend('Illuminate\Console\Command');
$this->expectations[] = expect('App\Console\Commands')
->classes()
->toHaveMethod('handle');
$this->expectations[] = expect('App')
->not->toExtend('Illuminate\Console\Command')
->ignoring('App\Console\Commands');
$this->expectations[] = expect('App\Mail')
->classes()
->toExtend('Illuminate\Mail\Mailable');
$this->expectations[] = expect('App\Mail')
->classes()
->toImplement('Illuminate\Contracts\Queue\ShouldQueue');
$this->expectations[] = expect('App')
->not->toExtend('Illuminate\Mail\Mailable')
->ignoring('App\Mail');
$this->expectations[] = expect('App\Jobs')
->classes()
->toImplement('Illuminate\Contracts\Queue\ShouldQueue');
$this->expectations[] = expect('App\Jobs')
->classes()
->toHaveMethod('handle');
$this->expectations[] = expect('App\Listeners')
->toHaveMethod('handle');
$this->expectations[] = expect('App\Notifications')
->classes()
->toExtend('Illuminate\Notifications\Notification');
$this->expectations[] = expect('App')
->not->toExtend('Illuminate\Notifications\Notification')
->ignoring('App\Notifications');
$this->expectations[] = expect('App\Providers')
->toHaveSuffix('ServiceProvider');
$this->expectations[] = expect('App\Providers')
->classes()
->toExtend('Illuminate\Support\ServiceProvider');
$this->expectations[] = expect('App\Providers')
->not->toBeUsed();
$this->expectations[] = expect('App')
->not->toExtend('Illuminate\Support\ServiceProvider')
->ignoring('App\Providers');
$this->expectations[] = expect('App')
->not->toHaveSuffix('ServiceProvider')
->ignoring('App\Providers');
$this->expectations[] = expect('App')
->not->toHaveSuffix('Controller')
->ignoring('App\Http\Controllers');
$this->expectations[] = expect('App\Http\Controllers')
->classes()
->toHaveSuffix('Controller');
$this->expectations[] = expect('App\Http')
->toOnlyBeUsedIn(['App\Http', 'App\Providers']);
$this->expectations[] = expect('App\Http\Controllers')
->not->toHavePublicMethodsBesides(['__construct', '__invoke', 'index', 'show', 'create', 'store', 'edit', 'update', 'destroy', 'middleware']);
$this->expectations[] = expect([
'dd',
'ddd',
'dump',
'env',
'exit',
'ray',
])->not->toBeUsed();
$this->expectations[] = expect('App\Policies')
->classes()
->toHaveSuffix('Policy');
$this->expectations[] = expect('App\Attributes')
->classes()
->toImplement('Illuminate\Contracts\Container\ContextualAttribute')
->toHaveAttribute('Attribute')
->toHaveMethod('resolve');
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
/**
* @internal
*/
final class Php extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations[] = expect([
'debug_zval_dump',
'debug_backtrace',
'debug_print_backtrace',
'dump',
'ray',
'ds',
'die',
'goto',
'global',
'var_dump',
'phpinfo',
'echo',
'ereg',
'eregi',
'mysql_connect',
'mysql_pconnect',
'mysql_query',
'mysql_select_db',
'mysql_fetch_array',
'mysql_fetch_assoc',
'mysql_fetch_object',
'mysql_fetch_row',
'mysql_num_rows',
'mysql_affected_rows',
'mysql_free_result',
'mysql_insert_id',
'mysql_error',
'mysql_real_escape_string',
'print',
'print_r',
'var_export',
'xdebug_break',
'xdebug_call_class',
'xdebug_call_file',
'xdebug_call_int',
'xdebug_call_line',
'xdebug_code_coverage_started',
'xdebug_connect_to_client',
'xdebug_debug_zval',
'xdebug_debug_zval_stdout',
'xdebug_dump_superglobals',
'xdebug_get_code_coverage',
'xdebug_get_collected_errors',
'xdebug_get_function_count',
'xdebug_get_function_stack',
'xdebug_get_gc_run_count',
'xdebug_get_gc_total_collected_roots',
'xdebug_get_gcstats_filename',
'xdebug_get_headers',
'xdebug_get_monitored_functions',
'xdebug_get_profiler_filename',
'xdebug_get_stack_depth',
'xdebug_get_tracefile_name',
'xdebug_info',
'xdebug_is_debugger_active',
'xdebug_memory_usage',
'xdebug_notify',
'xdebug_peak_memory_usage',
'xdebug_print_function_stack',
'xdebug_set_filter',
'xdebug_start_code_coverage',
'xdebug_start_error_collection',
'xdebug_start_function_monitor',
'xdebug_start_gcstats',
'xdebug_start_trace',
'xdebug_stop_code_coverage',
'xdebug_stop_error_collection',
'xdebug_stop_function_monitor',
'xdebug_stop_gcstats',
'xdebug_stop_trace',
'xdebug_time_index',
'xdebug_var_dump',
'trap',
])->not->toBeUsed();
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Expectation;
/**
* @internal
*/
final class Relaxed extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->eachUserNamespace(
fn (Expectation $namespace): ArchExpectation => $namespace->not->toUseStrictTypes(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeFinal(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHavePrivateMethods(),
);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
/**
* @internal
*/
final class Security extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations[] = expect([
'md5',
'sha1',
'uniqid',
'rand',
'mt_rand',
'tempnam',
'str_shuffle',
'shuffle',
'array_rand',
'eval',
'exec',
'shell_exec',
'system',
'passthru',
'create_function',
'unserialize',
'extract',
'mb_parse_str',
'dl',
'assert',
])->not->toBeUsed();
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Expectation;
/**
* @internal
*/
final class Strict extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->eachUserNamespace(
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toHaveProtectedMethods(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->not->toBeAbstract(),
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictTypes(),
fn (Expectation $namespace): ArchExpectation => $namespace->toUseStrictEquality(),
fn (Expectation $namespace): ArchExpectation => $namespace->classes()->toBeFinal(),
);
$this->expectations[] = expect([
'sleep',
'usleep',
])->not->toBeUsed();
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ final class BootExcludeList implements Bootstrapper
*
* @var array<int, non-empty-string>
*/
private const EXCLUDE_LIST = [
private const array EXCLUDE_LIST = [
'bin',
'overrides',
'resources',
+10 -2
View File
@@ -5,14 +5,16 @@ declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use Pest\Exceptions\FatalException;
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;
use function Pest\testDirectory;
/**
* @internal
*/
@@ -23,7 +25,7 @@ final class BootFiles implements Bootstrapper
*
* @var array<int, string>
*/
private const STRUCTURE = [
private const array STRUCTURE = [
'Expectations',
'Expectations.php',
'Helpers',
@@ -39,6 +41,10 @@ final class BootFiles implements Bootstrapper
$rootPath = TestSuite::getInstance()->rootPath;
$testsPath = $rootPath.DIRECTORY_SEPARATOR.testDirectory();
if (! is_dir($testsPath)) {
throw new FatalException(sprintf('The test directory [%s] does not exist.', $testsPath));
}
foreach (self::STRUCTURE as $filename) {
$filename = sprintf('%s%s%s', $testsPath, DIRECTORY_SEPARATOR, $filename);
@@ -77,6 +83,8 @@ final class BootFiles implements Bootstrapper
private function bootDatasets(string $testsPath): void
{
assert($testsPath !== '');
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
foreach ($files as $file) {
+2 -2
View File
@@ -12,13 +12,13 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BootKernelDump implements Bootstrapper
final readonly class BootKernelDump implements Bootstrapper
{
/**
* Creates a new Boot Kernel Dump instance.
*/
public function __construct(
private readonly OutputInterface $output,
private OutputInterface $output,
) {
// ...
}
+5 -3
View File
@@ -17,14 +17,16 @@ final class BootOverrides implements Bootstrapper
*
* @var array<int, string>
*/
private const FILES = [
public const array FILES = [
'Runner/Filter/NameFilterIterator.php',
'Runner/ResultCache/DefaultResultCache.php',
'Runner/TestSuiteLoader.php',
'TextUI/Command/WarmCodeCoverageCacheCommand.php',
'TextUI/Output/Default/ProgressPrinter/TestSkippedSubscriber.php',
'Runner/TestSuiteSorter.php',
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
'TextUI/TestSuiteFilterProcessor.php',
'Event/Value/ThrowableBuilder.php',
'Logging/JUnit/JunitXmlLogger.php',
];
/**
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Pest\Bootstrappers;
use Pest\Contracts\Bootstrapper;
use PHPUnit\TextUI\Configuration\Builder;
/**
* @internal
*/
final class BootPhpUnitConfiguration implements Bootstrapper
{
public function boot(): void
{
(new Builder)->build(['pest']);
}
}
+15 -5
View File
@@ -13,27 +13,37 @@ use PHPUnit\Event\Subscriber;
/**
* @internal
*/
final class BootSubscribers implements Bootstrapper
final readonly class BootSubscribers implements Bootstrapper
{
/**
* The list of Subscribers.
*
* @var array<int, class-string<Subscriber>>
*/
private const SUBSCRIBERS = [
private const array SUBSCRIBERS = [
Subscribers\EnsureConfigurationIsAvailable::class,
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
Subscribers\EnsureKernelDumpIsFlushed::class,
Subscribers\EnsureTeamCityEnabled::class,
Subscribers\EnsureTiaIsRunningPestTestsOnly::class,
Subscribers\EnsureTiaStarts::class,
Subscribers\EnsureTiaEnds::class,
Subscribers\EnsureTiaResultsAreCollected::class,
Subscribers\EnsureTiaResultIsRecordedOnPassed::class,
Subscribers\EnsureTiaResultIsRecordedOnFailed::class,
Subscribers\EnsureTiaResultIsRecordedOnErrored::class,
Subscribers\EnsureTiaResultIsRecordedOnSkipped::class,
Subscribers\EnsureTiaResultIsRecordedOnIncomplete::class,
Subscribers\EnsureTiaResultIsRecordedOnRisky::class,
Subscribers\EnsureTiaAssertionsAreRecordedOnFinished::class,
];
/**
* Creates a new instance of the Boot Subscribers.
*/
public function __construct(
private readonly Container $container,
) {
}
private Container $container,
) {}
/**
* Boots the list of Subscribers.
+2 -2
View File
@@ -11,13 +11,13 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BootView implements Bootstrapper
final readonly class BootView implements Bootstrapper
{
/**
* Creates a new instance of the Boot View.
*/
public function __construct(
private readonly OutputInterface $output
private OutputInterface $output
) {
// ..
}
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Pest\Collision;
use NunoMaduro\Collision\Adapters\Phpunit\TestResult;
use Pest\Configuration\Project;
use Symfony\Component\Console\Output\OutputInterface;
use function Termwind\render;
use function Termwind\renderUsing;
/**
* @internal
*/
final class Events
{
/**
* Sets the output.
*/
private static ?OutputInterface $output = null;
/**
* Sets the output.
*/
public static function setOutput(OutputInterface $output): void
{
self::$output = $output;
}
/**
* Fires before the test method description is printed.
*/
public static function beforeTestMethodDescription(TestResult $result, string $description): string
{
if (($context = $result->context) === []) {
return $description;
}
renderUsing(self::$output);
[
'assignees' => $assignees,
'issues' => $issues,
'prs' => $prs,
] = $context;
if (($link = Project::getInstance()->issues) !== '') {
$issuesDescription = array_map(fn (int $issue): string => sprintf('<a href="%s">#%s</a>', sprintf($link, $issue), $issue), $issues);
}
if (($link = Project::getInstance()->prs) !== '') {
$prsDescription = array_map(fn (int $pr): string => sprintf('<a href="%s">#%s</a>', sprintf($link, $pr), $pr), $prs);
}
if (($link = Project::getInstance()->assignees) !== '' && count($assignees) > 0) {
$assigneesDescription = array_map(fn (string $assignee): string => sprintf(
'<a href="%s">@%s</a>',
sprintf($link, $assignee),
$assignee,
), $assignees);
}
if (count($assignees) > 0 || count($issues) > 0 || count($prs) > 0) {
$description .= ' '.implode(', ', array_merge(
$issuesDescription ?? [],
$prsDescription ?? [],
isset($assigneesDescription) ? ['['.implode(', ', $assigneesDescription).']'] : [],
));
}
return $description;
}
/**
* Fires after the test method description is printed.
*/
public static function afterTestMethodDescription(TestResult $result): void
{
if (($context = $result->context) === []) {
return;
}
renderUsing(self::$output);
[
'notes' => $notes,
] = $context;
foreach ($notes as $note) {
render(sprintf(<<<'HTML'
<div class="ml-2">
<span class="text-gray"> // %s</span>
</div>
HTML, $note,
));
}
}
}
+4
View File
@@ -8,6 +8,8 @@ use Closure;
/**
* @internal
*
* @template T of object
*/
trait Extendable
{
@@ -20,6 +22,8 @@ trait Extendable
/**
* Register a new extend.
*
* @param-closure-this T $extend
*/
public function extend(string $name, Closure $extend): void
{
+2 -2
View File
@@ -60,12 +60,12 @@ trait Pipeable
}
/**
* Get th list of pipes by the given name.
* Get the 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] ?? []);
return array_map(fn (Closure $pipe): Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
}
}
+333 -34
View File
@@ -5,14 +5,29 @@ declare(strict_types=1);
namespace Pest\Concerns;
use Closure;
use Pest\Exceptions\DatasetArgsCountMismatch;
use Pest\Exceptions\DatasetArgumentsMismatch;
use Pest\Panic;
use Pest\Plugins\Tia;
use Pest\Plugins\Tia\Collectors;
use Pest\Plugins\Tia\Enums\ReplayType;
use Pest\Plugins\Tia\Recorder;
use Pest\Preset;
use Pest\Support\ChainableClosure;
use Pest\Support\Container;
use Pest\Support\ExceptionTrace;
use Pest\Support\Reflection;
use Pest\Support\Shell;
use Pest\TestSuite;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Attributes\PostCondition;
use PHPUnit\Framework\IncompleteTest;
use PHPUnit\Framework\SkippedTest;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestCase\ExceptionExpectation;
use PHPUnit\Framework\TestCase\OutputBuffer;
use ReflectionException;
use ReflectionFunction;
use ReflectionParameter;
use Throwable;
/**
@@ -33,9 +48,51 @@ trait Testable
private static string $__latestDescription;
/**
* The test's describing, if any.
* The test's assignees.
*/
public ?string $__describing = null;
private static array $__latestAssignees = [];
/**
* The test's notes.
*/
private static array $__latestNotes = [];
/**
* The test's issues.
*
* @var array<int, int>
*/
private static array $__latestIssues = [];
/**
* The test's PRs.
*
* @var array<int, int>
*/
private static array $__latestPrs = [];
/**
* The test's describing, if any.
*
* @var array<int, string>
*/
public array $__describing = [];
/**
* Whether the test has ran or not.
*/
public bool $__ran = false;
/**
* The active replay mode for this test, set in `setUp()` and checked
* in `__runTest()` / `tearDown()` to skip the body and after-each.
*/
private ReplayType $__replay = ReplayType::None;
/**
* The cached assertion count to replay, captured when entering replay mode.
*/
private int $__replayAssertions = 0;
/**
* The test's test closure.
@@ -62,6 +119,11 @@ trait Testable
*/
private static ?Closure $__afterAll = null;
/**
* The list of snapshot changes, if any.
*/
private array $__snapshotChanges = [];
/**
* Resets the test case static properties.
*/
@@ -72,20 +134,15 @@ trait Testable
}
/**
* Creates a new Test Case instance.
* Adds a new "note" to the Test Case.
*/
public function __construct(string $name)
public function note(array|string $note): self
{
parent::__construct($name);
$note = is_array($note) ? $note : [$note];
$test = TestSuite::getInstance()->tests->get(self::$__filename);
self::$__latestNotes = array_merge(self::$__latestNotes, $note);
if ($test->hasMethod($name)) {
$method = $test->getMethod($name);
$this->__description = self::$__latestDescription = $method->description;
$this->__describing = $method->describing;
$this->__test = $method->getClosure($this);
}
return $this;
}
/**
@@ -93,7 +150,7 @@ trait Testable
*/
public function __addBeforeAll(?Closure $hook): void
{
if (! $hook instanceof \Closure) {
if (! $hook instanceof Closure) {
return;
}
@@ -107,7 +164,7 @@ trait Testable
*/
public function __addAfterAll(?Closure $hook): void
{
if (! $hook instanceof \Closure) {
if (! $hook instanceof Closure) {
return;
}
@@ -137,7 +194,7 @@ trait Testable
*/
private function __addHook(string $property, ?Closure $hook): void
{
if (! $hook instanceof \Closure) {
if (! $hook instanceof Closure) {
return;
}
@@ -159,7 +216,11 @@ trait Testable
$beforeAll = ChainableClosure::boundStatically(self::$__beforeAll, $beforeAll);
}
try {
call_user_func(Closure::bind($beforeAll, null, self::class));
} catch (Throwable $e) {
Panic::with($e);
}
}
/**
@@ -181,13 +242,20 @@ trait Testable
/**
* Gets executed before the Test Case.
*/
protected function setUp(): void
protected function setUp(...$arguments): void
{
TestSuite::getInstance()->test = $this;
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
$description = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description;
$description = $method->description;
if ($this->dataName()) {
$description = str_contains((string) $description, ':dataset')
? str_replace(':dataset', str_replace('dataset ', '', $this->dataName()), (string) $description)
: $description.' with '.$this->dataName();
}
$description = htmlspecialchars(html_entity_decode((string) $description), ENT_NOQUOTES);
if ($method->repetitions > 1) {
$matches = [];
@@ -205,34 +273,110 @@ trait Testable
}
$this->__description = self::$__latestDescription = $description;
self::$__latestAssignees = $method->assignees;
self::$__latestNotes = $method->notes;
self::$__latestIssues = $method->issues;
self::$__latestPrs = $method->prs;
/** @var Tia $tia */
$tia = Container::getInstance()->get(Tia::class);
$status = $tia->getStatus(self::$__filename, $this::class.'::'.$this->name());
$replay = ReplayType::fromStatus($status);
if ($replay !== ReplayType::None) {
assert($status !== null);
match ($replay) {
ReplayType::Pass, ReplayType::Risky => $this->__beginReplay($replay, $tia),
ReplayType::Skipped => $this->markTestSkipped($status->message()),
ReplayType::Incomplete => $this->markTestIncomplete($status->message()),
ReplayType::Failure => throw new AssertionFailedError($status->message() ?: 'Cached failure'),
};
return;
}
$recorder = Container::getInstance()->get(Recorder::class);
assert($recorder instanceof Recorder);
if ($recorder->isActive()) {
$recorder->beginTest($this::class, $this->name(), self::$__filename);
}
parent::setUp();
Collectors::armAll($recorder);
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1];
if ($this->__beforeEach instanceof Closure) {
$beforeEach = ChainableClosure::bound($this->__beforeEach, $beforeEach);
}
$this->__callClosure($beforeEach, func_get_args());
$this->__callClosure($beforeEach, $arguments);
}
private function __beginReplay(ReplayType $replay, Tia $tia): void
{
$this->__replay = $replay;
$this->__replayAssertions = $tia->getAssertionCount($this::class.'::'.$this->name());
$this->__ran = true;
}
/**
* Initialize test case properties from TestSuite.
*/
public function __initializeTestCase(): void
{
// Return if the test case has already been initialized
if (isset($this->__test)) {
return;
}
$name = $this->name();
$test = TestSuite::getInstance()->tests->get(self::$__filename);
if ($test->hasMethod($name)) {
$method = $test->getMethod($name);
$this->__description = self::$__latestDescription = $method->description;
self::$__latestAssignees = $method->assignees;
self::$__latestNotes = $method->notes;
self::$__latestIssues = $method->issues;
self::$__latestPrs = $method->prs;
$this->__describing = $method->describing;
$this->__test = $method->getClosure();
$method->setUp($this);
}
}
/**
* Gets executed after the Test Case.
*/
protected function tearDown(): void
protected function tearDown(...$arguments): void
{
if ($this->__replay !== ReplayType::None) {
TestSuite::getInstance()->test = null;
return;
}
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
if ($this->__afterEach instanceof Closure) {
$afterEach = ChainableClosure::bound($this->__afterEach, $afterEach);
}
try {
$this->__callClosure($afterEach, func_get_args());
} finally {
parent::tearDown();
TestSuite::getInstance()->test = null;
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
$method->tearDown($this);
}
}
/**
@@ -242,12 +386,108 @@ trait Testable
*/
private function __runTest(Closure $closure, ...$args): mixed
{
$arguments = $this->__resolveTestArguments($args);
$this->__ensureDatasetArgumentNumberMatches($arguments);
if ($this->__replay === ReplayType::Pass || $this->__replay === ReplayType::Risky) {
if ($this->__replay === ReplayType::Pass && $this->__replayAssertions === 0) {
$this->expectNotToPerformAssertions();
}
$this->addToAssertionCount($this->__replayAssertions);
return null;
}
$arguments = $this->__resolveTestArguments($args);
$this->__ensureDatasetArgumentNameAndNumberMatches($arguments);
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
if ($method->flakyTries === null) {
return $this->__callClosure($closure, $arguments);
}
$lastException = null;
$initialProperties = get_object_vars($this);
for ($attempt = 1; $attempt <= $method->flakyTries; $attempt++) {
try {
return $this->__callClosure($closure, $arguments);
} catch (Throwable $e) {
if ($e instanceof SkippedTest
|| $e instanceof IncompleteTest
|| $this->__isExpectedException($e)) {
throw $e;
}
$lastException = $e;
if ($attempt < $method->flakyTries) {
if ($this->__snapshotChanges !== []) {
throw $e;
}
$this->tearDown();
Closure::bind(fn (): array => $this->mockObjects = [], $this, TestCase::class)();
foreach (array_keys(array_diff_key(get_object_vars($this), $initialProperties)) as $property) {
unset($this->{$property});
}
$outputBuffer = Closure::bind(fn () => $this->outputBuffer, $this, TestCase::class)();
if ($outputBuffer->hasExpectation()) {
ob_clean();
Closure::bind(function (): void {
$this->expectedString = null;
$this->expectedRegularExpression = null;
}, $outputBuffer, OutputBuffer::class)();
}
$this->setUp();
}
}
}
throw $lastException;
}
/**
* Determines if the given exception matches PHPUnit's expected exception.
*/
private function __isExpectedException(Throwable $e): bool
{
$expectation = Closure::bind(fn () => $this->exceptionExpectation, $this, TestCase::class)();
$read = fn (string $property): mixed => Closure::bind(fn () => $this->{$property}, $expectation, ExceptionExpectation::class)();
$expectedClass = $read('expectedException');
if ($expectedClass !== null) {
return $e instanceof $expectedClass;
}
$expectedMessage = $read('expectedMessage');
if ($expectedMessage !== null) {
return str_contains($e->getMessage(), (string) $expectedMessage);
}
$expectedMessageRegex = $read('expectedMessageRegularExpression');
if ($expectedMessageRegex !== null) {
return preg_match($expectedMessageRegex, $e->getMessage()) === 1;
}
$expectedCode = $read('expectedCode');
if ($expectedCode !== null) {
return $e->getCode() === $expectedCode;
}
return false;
}
/**
* Resolve the passed arguments. Any Closures will be bound to the testcase and resolved.
*
@@ -258,11 +498,17 @@ trait Testable
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
if ($method->repetitions > 1) {
array_shift($arguments);
// If the test is repeated, the first argument is the iteration number
// we need to move it to the end of the arguments list
// so that the datasets are the first n arguments
// and the iteration number is the last argument
$firstArgument = array_shift($arguments);
$arguments[] = $firstArgument;
}
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
$testParameterTypesByName = Reflection::getFunctionArguments($underlyingTest);
$testParameterTypes = array_values($testParameterTypesByName);
if (count($arguments) !== 1) {
foreach ($arguments as $argumentIndex => $argumentValue) {
@@ -270,7 +516,11 @@ trait Testable
continue;
}
if (in_array($testParameterTypes[$argumentIndex], [Closure::class, 'callable', 'mixed'])) {
$parameterType = is_string($argumentIndex)
? $testParameterTypesByName[$argumentIndex]
: $testParameterTypes[$argumentIndex];
if (in_array($parameterType, [Closure::class, 'callable', 'mixed'])) {
continue;
}
@@ -280,11 +530,11 @@ trait Testable
return $arguments;
}
if (! $arguments[0] instanceof Closure) {
if (! isset($arguments[0]) || ! $arguments[0] instanceof Closure) {
return $arguments;
}
if (in_array($testParameterTypes[0], [Closure::class, 'callable'])) {
if (isset($testParameterTypes[0]) && in_array($testParameterTypes[0], [Closure::class, 'callable'])) {
return $arguments;
}
@@ -296,16 +546,16 @@ trait Testable
return [$boundDatasetResult];
}
return array_values($boundDatasetResult);
return $boundDatasetResult;
}
/**
* Ensures dataset items count matches underlying test case required parameters
*
* @throws ReflectionException
* @throws DatasetArgsCountMismatch
* @throws DatasetArgumentsMismatch
*/
private function __ensureDatasetArgumentNumberMatches(array $arguments): void
private function __ensureDatasetArgumentNameAndNumberMatches(array $arguments): void
{
if ($arguments === []) {
return;
@@ -316,11 +566,21 @@ trait Testable
$requiredParametersCount = $testReflection->getNumberOfRequiredParameters();
$suppliedParametersCount = count($arguments);
if ($suppliedParametersCount >= $requiredParametersCount) {
$datasetParameterNames = array_keys($arguments);
$testParameterNames = array_map(
fn (ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(),
array_filter($testReflection->getParameters(), fn (ReflectionParameter $reflectionParameter): bool => ! $reflectionParameter->isOptional()),
);
if (array_diff($testParameterNames, $datasetParameterNames) === []) {
return;
}
throw new DatasetArgsCountMismatch($requiredParametersCount, $suppliedParametersCount);
if (isset($testParameterNames[0]) && $suppliedParametersCount >= $requiredParametersCount) {
return;
}
throw new DatasetArgumentsMismatch($requiredParametersCount, $suppliedParametersCount);
}
/**
@@ -331,6 +591,24 @@ trait Testable
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
}
/**
* Uses the given preset on the test.
*/
public function preset(): Preset
{
return new Preset;
}
#[PostCondition]
protected function __MarkTestIncompleteIfSnapshotHaveChanged(): void
{
if (count($this->__snapshotChanges) === 0) {
return;
}
$this->markTestIncomplete(implode('. ', $this->__snapshotChanges));
}
/**
* The printable test case name.
*/
@@ -352,6 +630,27 @@ trait Testable
*/
public static function getLatestPrintableTestCaseMethodName(): string
{
return self::$__latestDescription;
return self::$__latestDescription ?? '';
}
/**
* The printable test case method context.
*/
public static function getPrintableContext(): array
{
return [
'assignees' => self::$__latestAssignees,
'issues' => self::$__latestIssues,
'prs' => self::$__latestPrs,
'notes' => self::$__latestNotes,
];
}
/**
* Opens a shell for the test case.
*/
public function shell(): void
{
Shell::open();
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace Pest;
use Pest\PendingCalls\BeforeEachCall;
use Pest\PendingCalls\UsesCall;
/**
* @internal
*
* @mixin UsesCall
*/
final readonly class Configuration
{
/**
* The filename of the configuration.
*/
private string $filename;
/**
* Creates a new configuration instance.
*/
public function __construct(
string $filename,
) {
$this->filename = str_ends_with($filename, DIRECTORY_SEPARATOR.'Pest.php') ? dirname($filename) : $filename;
}
/**
* Use the given classes and traits in the given targets.
*/
public function in(string ...$targets): UsesCall
{
return new UsesCall($this->filename, [])->in(...$targets);
}
/**
* Depending on where is called, it will extend the given classes and traits globally or locally.
*/
public function extend(string ...$classAndTraits): UsesCall
{
return new UsesCall(
$this->filename,
array_values($classAndTraits)
);
}
/**
* Depending on where is called, it will extend the given classes and traits globally or locally.
*/
public function extends(string ...$classAndTraits): UsesCall
{
return $this->extend(...$classAndTraits);
}
/**
* Depending on where is called, it will add the given groups globally or locally.
*/
public function group(string ...$groups): UsesCall
{
return new UsesCall($this->filename, [])->group(...$groups);
}
/**
* Marks all tests in the current file to be run exclusively.
*/
public function only(): void
{
new BeforeEachCall(TestSuite::getInstance(), $this->filename)->only();
}
/**
* Depending on where is called, it will extend the given classes and traits globally or locally.
*/
public function use(string ...$classAndTraits): UsesCall
{
return $this->extend(...$classAndTraits);
}
/**
* Depending on where is called, it will extend the given classes and traits globally or locally.
*/
public function uses(string ...$classAndTraits): UsesCall
{
return $this->extends(...$classAndTraits);
}
/**
* Gets the printer configuration.
*/
public function printer(): Configuration\Printer
{
return new Configuration\Printer;
}
/**
* Gets the presets configuration.
*/
public function presets(): Configuration\Presets
{
return new Configuration\Presets;
}
/**
* Gets the project configuration.
*/
public function project(): Configuration\Project
{
return Configuration\Project::getInstance();
}
/**
* Gets the browser configuration.
*/
public function browser(): Browser\Configuration
{
return new Browser\Configuration;
}
/**
* Gets the evals configuration.
*/
public function evals(): Evals\Configuration
{
return new Evals\Configuration;
}
/**
* Gets the TIA (Test Impact Analysis) configuration.
*/
public function tia(): Plugins\Tia\Configuration
{
return new Plugins\Tia\Configuration;
}
/**
* Proxies calls to the uses method.
*
* @param array<array-key, mixed> $arguments
*/
public function __call(string $name, array $arguments): mixed
{
return $this->uses()->$name(...$arguments); // @phpstan-ignore-line
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Pest\Configuration;
use Closure;
use Pest\Preset;
final class Presets
{
/**
* Creates a custom preset instance, and adds it to the list of presets.
*/
public function custom(string $name, Closure $execute): void
{
Preset::custom($name, $execute);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Pest\Configuration;
use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
/**
* @internal
*/
final readonly class Printer
{
/**
* Sets the theme to compact.
*/
public function compact(): self
{
DefaultPrinter::compact(true);
return $this;
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Pest\Configuration;
/**
* @internal
*/
final class Project
{
/**
* The assignees link.
*
* @internal
*/
public string $assignees = '';
/**
* The issues link.
*
* @internal
*/
public string $issues = '';
/**
* The PRs link.
*
* @internal
*/
public string $prs = '';
/**
* The singleton instance.
*/
private static ?self $instance = null;
/**
* Creates a new instance of the project.
*/
public static function getInstance(): self
{
return self::$instance ??= new self;
}
/**
* Sets the test project to GitHub.
*/
public function github(string $project): self
{
$this->issues = "https://github.com/{$project}/issues/%s";
$this->prs = "https://github.com/{$project}/pull/%s";
$this->assignees = 'https://github.com/%s';
return $this;
}
/**
* Sets the test project to GitLab.
*/
public function gitlab(string $project, string $hostname = 'gitlab.com'): self
{
// Simple way to ensure only the host is used
$hostname = parse_url($hostname, PHP_URL_HOST) ?? $hostname;
$this->issues = "https://{$hostname}/{$project}/-/work_items/%s";
$this->prs = "https://{$hostname}/{$project}/-/merge_requests/%s";
$this->assignees = "https://{$hostname}/%s";
return $this;
}
/**
* Sets the test project to Bitbucket.
*/
public function bitbucket(string $project): self
{
$this->issues = "https://bitbucket.org/{$project}/issues/%s";
$this->prs = "https://bitbucket.org/{$project}/pull-requests/%s";
$this->assignees = 'https://bitbucket.org/%s';
return $this;
}
/**
* Sets the test project to Jira.
*/
public function jira(string $namespace, string $project): self
{
$this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s";
$this->assignees = "https://{$namespace}.atlassian.net/secure/ViewProfile.jspa?name=%s";
return $this;
}
/**
* Sets the test project to custom.
*/
public function custom(string $issues, string $prs, string $assignees): self
{
$this->issues = $issues;
$this->prs = $prs;
$this->assignees = $assignees;
return $this;
}
}
+3 -3
View File
@@ -9,14 +9,14 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class Help
final readonly class Help
{
/**
* The Command messages.
*
* @var array<int, string>
*/
private const HELP_MESSAGES = [
private const array HELP_MESSAGES = [
'<comment>Pest Options:</comment>',
' <info>--init</info> Initialise a standard Pest configuration',
' <info>--coverage</info> Enable coverage and output to standard output',
@@ -27,7 +27,7 @@ final class Help
/**
* Creates a new Console Command instance.
*/
public function __construct(private readonly OutputInterface $output)
public function __construct(private OutputInterface $output)
{
// ..
}
+16 -11
View File
@@ -15,25 +15,30 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* @internal
*/
final class Thanks
final readonly class Thanks
{
/**
* The support options.
*
* @var array<string, string>
*/
private const FUNDING_MESSAGES = [
'Star the project on GitHub' => 'https://github.com/pestphp/pest',
'Tweet about the project' => 'https://twitter.com/pestphp',
'Sponsor the project' => 'https://github.com/sponsors/nunomaduro',
private const array FUNDING_MESSAGES = [
'Star' => 'https://github.com/pestphp/pest',
'YouTube' => 'https://youtube.com/@nunomaduro',
'TikTok' => 'https://tiktok.com/@enunomaduro',
'Twitch' => 'https://twitch.tv/nunomaduro',
'LinkedIn' => 'https://linkedin.com/in/nunomaduro',
'Instagram' => 'https://instagram.com/enunomaduro',
'X' => 'https://x.com/enunomaduro',
'Sponsor' => 'https://github.com/sponsors/nunomaduro',
];
/**
* Creates a new Console Command instance.
*/
public function __construct(
private readonly InputInterface $input,
private readonly OutputInterface $output
private InputInterface $input,
private OutputInterface $output
) {
// ..
}
@@ -49,7 +54,7 @@ final class Thanks
$wantsToSupport = false;
if (getenv('PEST_NO_SUPPORT') !== 'true' && $this->input->isInteractive()) {
$wantsToSupport = (new SymfonyQuestionHelper())->ask(
$wantsToSupport = (new SymfonyQuestionHelper)->ask(
new ArrayInput([]),
$this->output,
new ConfirmationQuestion(
@@ -71,13 +76,13 @@ final class Thanks
}
if ($wantsToSupport === true) {
if (PHP_OS_FAMILY == 'Darwin') {
if (PHP_OS_FAMILY === 'Darwin') {
exec('open https://github.com/pestphp/pest');
}
if (PHP_OS_FAMILY == 'Windows') {
if (PHP_OS_FAMILY === 'Windows') {
exec('start https://github.com/pestphp/pest');
}
if (PHP_OS_FAMILY == 'Linux') {
if (PHP_OS_FAMILY === 'Linux') {
exec('xdg-open https://github.com/pestphp/pest');
}
}
-21
View File
@@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
use Pest\Factories\TestCaseMethodFactory;
/**
* @internal
*/
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;
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
/**
* @internal
*/
interface ArchPreset {}
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface HandlesOriginalArguments
{
/**
* Adds original arguments before the Test Suite execution.
*
* @param array<int, string> $arguments
*/
public function handleOriginalArguments(array $arguments): void;
}
@@ -7,10 +7,10 @@ namespace Pest\Contracts\Plugins;
/**
* @internal
*/
interface Shutdownable
interface Terminable
{
/**
* Shutdowns the plugin.
* Terminates the plugin.
*/
public function shutdown(): void;
public function terminate(): void;
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
/**
* @internal
*/
interface Restarter
{
/**
* @param array<int, string> $arguments
*/
public function maybeRestart(string $projectRoot, array $arguments): void;
}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Pest\Evaluators;
use Pest\Factories\Attribute;
/**
* @internal
*/
final class Attributes
{
/**
* Evaluates the given attributes and returns the code.
*
* @param iterable<int, Attribute> $attributes
*/
public static function code(iterable $attributes): string
{
return implode(PHP_EOL, array_map(function (Attribute $attribute): string {
$name = $attribute->name;
if ($attribute->arguments === []) {
return " #[\\{$name}]";
}
$arguments = array_map(fn (string $argument): string => var_export($argument, true), iterator_to_array($attribute->arguments));
return sprintf(' #[\\%s(%s)]', $name, implode(', ', $arguments));
}, iterator_to_array($attributes)));
}
}
@@ -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 AfterBeforeTestFunction extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new Exception instance.
*/
public function __construct(string $filename)
{
parent::__construct('After method cannot be used with before the [test|it] functions in the filename `['.$filename.']`.');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Pest\Contracts\Panicable;
use Pest\Support\View;
use RuntimeException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class BaselineFetchFailed extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
{
public function __construct(
private readonly string $headline,
private readonly string $hint,
private readonly bool $hasAnchor = false,
) {
parent::__construct($headline);
}
public function render(OutputInterface $output): void
{
View::renderUsing($output);
if (! $this->hasAnchor) {
View::render('components.badge', ['type' => 'ERROR', 'content' => $this->headline]);
$this->renderChild($output, $this->hint.' Or use [--fresh] to record locally.');
$output->writeln('');
return;
}
$this->renderChild($output, $this->headline);
$this->renderChild($output, $this->hint.' Or use [--fresh] to record locally.');
$output->writeln('');
}
public function exitCode(): int
{
return 1;
}
private function renderChild(OutputInterface $output, string $text): void
{
$output->writeln(sprintf(' <fg=gray>─ %s</>', $text));
}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use Exception;
final class DatasetArgsCountMismatch extends Exception
{
public function __construct(int $requiredCount, int $suppliedCount)
{
parent::__construct(sprintf('Test expects %d arguments but dataset only provides %d', $requiredCount, $suppliedCount));
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use Exception;
final class DatasetArgumentsMismatch extends Exception
{
public function __construct(int $requiredCount, int $suppliedCount)
{
if ($requiredCount <= $suppliedCount) {
parent::__construct('Test argument names and dataset keys do not match');
} else {
parent::__construct(sprintf('Test expects %d arguments but dataset only provides %d', $requiredCount, $suppliedCount));
}
}
//
}
+1 -1
View File
@@ -22,7 +22,7 @@ final class DatasetMissing extends BadFunctionCallException implements Exception
public function __construct(string $file, string $name, array $arguments)
{
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,
count($arguments),
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use RuntimeException;
/**
* @internal
*/
final class FatalException extends RuntimeException implements RenderlessTrace
{
//
}
+32
View File
@@ -0,0 +1,32 @@
<?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 NoAffectedTestsFound extends InvalidArgumentException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
{
public function render(OutputInterface $output): void
{
$output->writeln([
'',
' <fg=white;options=bold;bg=blue> INFO </> No affected tests found.',
'',
]);
}
public function exitCode(): int
{
return 0;
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class NoDirtyTestsFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace, Panicable
final class NoDirtyTestsFound extends InvalidArgumentException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
{
/**
* Renders the panic on the given output.
+1 -1
View File
@@ -20,7 +20,7 @@ final class ShouldNotHappen extends RuntimeException
$message = $exception->getMessage();
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/issues
Issue: %s
PHP version: %s
+6 -2
View File
@@ -19,7 +19,11 @@ final class TestCaseAlreadyInUse extends InvalidArgumentException implements Exc
*/
public function __construct(string $inUse, string $newOne, string $folder)
{
parent::__construct(sprintf('Test case `%s` can not be used. The folder `%s` already uses the test case `%s`',
$newOne, $folder, $inUse));
parent::__construct(sprintf(
'Test case [%s] can not be used. The folder [%s] already uses the test case [%s].',
$newOne,
$folder,
$inUse,
));
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use InvalidArgumentException;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Pest\Factories\TestCaseMethodFactory;
use Symfony\Component\Console\Exception\ExceptionInterface;
/**
* @internal
*/
final class TestClosureMustNotBeStatic extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new Exception instance.
*/
public function __construct(TestCaseMethodFactory $method)
{
parent::__construct(
sprintf(
'Test closure must not be static. Please remove the [static] keyword from the [%s] method in [%s].',
$method->description,
$method->filename
)
);
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Pest\Exceptions;
use NunoMaduro\Collision\Contracts\RenderlessEditor;
use NunoMaduro\Collision\Contracts\RenderlessTrace;
use Pest\Contracts\Panicable;
use RuntimeException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class TiaRequiresPestTests extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
{
public function __construct(private readonly string $className, string $filename)
{
parent::__construct(sprintf(
'Tia mode requires only functional based Pest tests, but encountered PHPUnit class [%s] in [%s].',
$className,
$filename,
));
}
public function render(OutputInterface $output): void
{
$output->writeln([
'',
' <fg=white;options=bold;bg=red> ERROR </> Tia mode requires Pest tests.',
'',
sprintf(' Encountered PHPUnit class <fg=yellow>%s</>', $this->className),
sprintf(' in <fg=gray>%s</>.', $this->file),
'',
' Convert it to a Pest test, or run without Tia.',
'',
]);
}
public function exitCode(): int
{
return 1;
}
}
+531 -63
View File
@@ -4,8 +4,11 @@ declare(strict_types=1);
namespace Pest;
use Attribute;
use BadMethodCallException;
use Closure;
use InvalidArgumentException;
use OutOfRangeException;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Arch\Expectations\Targeted;
use Pest\Arch\Expectations\ToBeUsedIn;
@@ -15,6 +18,7 @@ use Pest\Arch\Expectations\ToOnlyUse;
use Pest\Arch\Expectations\ToUse;
use Pest\Arch\Expectations\ToUseNothing;
use Pest\Arch\PendingArchExpectation;
use Pest\Arch\Support\Composer;
use Pest\Arch\Support\FileLineFinder;
use Pest\Concerns\Extendable;
use Pest\Concerns\Pipeable;
@@ -27,13 +31,14 @@ use Pest\Expectations\HigherOrderExpectation;
use Pest\Expectations\OppositeExpectation;
use Pest\Matchers\Any;
use Pest\Support\ExpectationPipeline;
use Pest\Support\Reflection;
use PHPUnit\Architecture\Elements\ObjectDescription;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\ExpectationFailedException;
use ReflectionEnum;
use ReflectionMethod;
use ReflectionProperty;
/**
* @internal
*
* @template TValue
*
* @property OppositeExpectation $not Creates the opposite expectation.
@@ -48,7 +53,9 @@ use PHPUnit\Framework\ExpectationFailedException;
*/
final class Expectation
{
/** @use Extendable<self<TValue>> */
use Extendable;
use Pipeable;
use Retrievable;
@@ -105,7 +112,7 @@ final class Expectation
if (function_exists('dump')) {
dump($this->value, ...$arguments);
} else {
var_dump($this->value);
var_dump($this->value, ...$arguments);
}
return $this;
@@ -113,20 +120,60 @@ final class Expectation
/**
* Dump the expectation value and end the script.
*
* @return never
*/
public function dd(mixed ...$arguments): void
public function dd(mixed ...$arguments): never
{
if (function_exists('dd')) {
dd($this->value, ...$arguments);
}
var_dump($this->value);
if (getenv('PARATEST') !== false || isset($_SERVER['COLLISION_PRINTER'])) {
ob_start();
var_dump($this->value, ...$arguments);
$output = ob_get_clean();
throw new ExpectationFailedException($output);
}
var_dump($this->value, ...$arguments);
exit(1);
}
/**
* Dump the expectation value when the result of the condition is truthy.
*
* @param (Closure(TValue): bool)|bool $condition
* @return self<TValue>
*/
public function ddWhen(Closure|bool $condition, mixed ...$arguments): Expectation
{
$condition = $condition instanceof Closure ? $condition($this->value) : $condition;
if (! $condition) {
return $this;
}
$this->dd(...$arguments);
}
/**
* Dump the expectation value when the result of the condition is falsy.
*
* @param (Closure(TValue): bool)|bool $condition
* @return self<TValue>
*/
public function ddUnless(Closure|bool $condition, mixed ...$arguments): Expectation
{
$condition = $condition instanceof Closure ? $condition($this->value) : $condition;
if ($condition) {
return $this;
}
$this->dd(...$arguments);
}
/**
* Send the expectation value to Ray along with all given arguments.
*
@@ -156,7 +203,7 @@ final class Expectation
*
* @return EachExpectation<TValue>
*/
public function each(callable $callback = null): EachExpectation
public function each(?callable $callback = null): EachExpectation
{
if (! is_iterable($this->value)) {
throw new BadMethodCallException('Expectation value is not iterable.');
@@ -185,30 +232,26 @@ final class Expectation
throw new BadMethodCallException('Expectation value is not iterable.');
}
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
$keys = array_keys($value);
$values = array_values($value);
$callbacksCount = count($callbacks);
$index = 0;
while (count($callbacks) < count($values)) {
$callbacks[] = $callbacks[$index];
$index = $index < count($values) - 1 ? $index + 1 : 0;
if ($callbacks === []) {
throw new InvalidArgumentException('No sequence expectations defined.');
}
if ($callbacksCount > count($values)) {
Assert::assertLessThanOrEqual(count($value), count($callbacks));
$index = $valuesCount = 0;
foreach ($this->value as $key => $value) {
$valuesCount++;
if ($callbacks[$index] instanceof Closure) {
$callbacks[$index](new self($value), new self($key));
} else {
new self($value)->toEqual($callbacks[$index]);
}
foreach ($values as $key => $item) {
if ($callbacks[$key] instanceof Closure) {
call_user_func($callbacks[$key], new self($item), new self($keys[$key]));
continue;
$index = isset($callbacks[$index + 1]) ? $index + 1 : 0;
}
(new self($item))->toEqual($callbacks[$key]);
if ($valuesCount < count($callbacks)) {
throw new OutOfRangeException('Sequence expectations are more than the iterable items.');
}
return $this;
@@ -230,7 +273,7 @@ final class Expectation
$matched = false;
foreach ($expressions as $key => $callback) {
if ($subject != $key) {
if ($subject != $key) { // @pest-arch-ignore-line
continue;
}
@@ -296,7 +339,7 @@ final class Expectation
* @param array<int, mixed> $parameters
* @return Expectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue>
*/
public function __call(string $method, array $parameters): Expectation|HigherOrderExpectation|PendingArchExpectation
public function __call(string $method, array $parameters): Expectation|HigherOrderExpectation|PendingArchExpectation|ArchExpectation
{
if (! self::hasMethod($method)) {
if (! is_object($this->value) && method_exists(PendingArchExpectation::class, $method)) {
@@ -317,9 +360,19 @@ final class Expectation
return new HigherOrderExpectation($this, call_user_func_array($this->value->$method(...), $parameters));
}
ExpectationPipeline::for($this->getExpectationClosure($method))
$closure = $this->getExpectationClosure($method);
$reflectionClosure = new \ReflectionFunction($closure);
$expectation = $reflectionClosure->getClosureThis();
if ($reflectionClosure->getReturnType()?->__toString() === ArchExpectation::class) {
return $closure(...$parameters);
}
assert(is_object($expectation));
ExpectationPipeline::for($closure)
->send(...$parameters)
->through($this->pipes($method, $this, Expectation::class))
->through($this->pipes($method, $expectation, Expectation::class))
->run();
return $this;
@@ -340,7 +393,7 @@ final class Expectation
if (self::hasExtend($name)) {
$extend = self::$extends[$name]->bindTo($this, Expectation::class);
if ($extend != false) {
if ($extend != false) { // @pest-arch-ignore-line
return $extend;
}
}
@@ -353,7 +406,7 @@ final class Expectation
*
* @return Expectation<TValue>|OppositeExpectation<TValue>|EachExpectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue|null>|TValue
*/
public function __get(string $name)
public function __get(string $name): mixed
{
if (! self::hasMethod($name)) {
if (! is_object($this->value) && method_exists(PendingArchExpectation::class, $name)) {
@@ -384,7 +437,7 @@ final class Expectation
*/
public function any(): Any
{
return new Any();
return new Any;
}
/**
@@ -397,6 +450,71 @@ final class Expectation
return ToUse::make($this, $targets);
}
/**
* Asserts that the given expectation target does have the given permissions
*/
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => substr(sprintf('%o', fileperms($object->path)), -4) === $permissions,
sprintf('permissions to be [%s]', $permissions),
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
/**
* Asserts that the given expectation target to have line count less than the given number.
*/
public function toHaveLineCountLessThan(int $lines): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => count(file($object->path)) < $lines, // @phpstan-ignore-line
sprintf('to have less than %d lines of code', $lines),
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
/**
* Asserts that the given expectation target have all methods documented.
*/
public function toHaveMethodsDocumented(): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter(
Reflection::getMethodsFromReflectionClass($object->reflectionClass),
fn (ReflectionMethod $method): bool => (enum_exists($object->name) === false || in_array($method->name, ['from', 'tryFrom', 'cases'], true) === false)
&& realpath($method->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $method->getDocComment() === false,
) === [],
'to have methods with documentation / annotations',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Asserts that the given expectation target have all properties documented.
*/
public function toHavePropertiesDocumented(): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter(
Reflection::getPropertiesFromReflectionClass($object->reflectionClass),
fn (ReflectionProperty $property): bool => (enum_exists($object->name) === false || in_array($property->name, ['value', 'name'], true) === false)
&& realpath($property->getDeclaringClass()->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $property->isPromoted() === false
&& $property->getDocComment() === false,
) === [],
'to have properties with documentation / annotations',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Asserts that the given expectation target use the "declare(strict_types=1)" declaration.
*/
@@ -404,12 +522,25 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => str_contains((string) file_get_contents($object->path), 'declare(strict_types=1);'),
fn (ObjectDescription $object): bool => (bool) preg_match('/^<\?php\s*(\/\*[\s\S]*?\*\/|\/\/[^\r\n]*(?:\r?\n|$)|\s)*declare\s*\(\s*strict_types\s*=\s*1\s*\)\s*;/m', (string) file_get_contents($object->path)),
'to use strict types',
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
/**
* Asserts that the given expectation target uses strict equality.
*/
public function toUseStrictEquality(): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), ' == ') && ! str_contains((string) file_get_contents($object->path), ' != '),
'to use strict equality',
FileLineFinder::where(fn (string $line): bool => str_contains($line, ' == ') || str_contains($line, ' != ')),
);
}
/**
* Asserts that the given expectation target is final.
*/
@@ -417,7 +548,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && $object->reflectionClass->isFinal(),
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && isset($object->reflectionClass) && $object->reflectionClass->isFinal(),
'to be final',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -430,7 +561,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && isset($object->reflectionClass) && $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line
'to be readonly',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -443,7 +574,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isTrait(),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isTrait(),
'to be trait',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -464,12 +595,122 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isAbstract(),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isAbstract(),
'to be abstract',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target has a specific method.
*
* @param array<int, string>|string $method
*/
public function toHaveMethod(array|string $method): ArchExpectation
{
$methods = is_array($method) ? $method : [$method];
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => count(array_filter($methods, fn (string $method): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod($method))) === count($methods),
sprintf("to have method '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target has a specific methods.
*
* @param array<int, string> $methods
*/
public function toHaveMethods(array $methods): ArchExpectation
{
return $this->toHaveMethod($methods);
}
/**
* Not supported.
*/
public function toHavePublicMethodsBesides(): void
{
throw InvalidExpectation::fromMethods(['toHavePublicMethodsBesides']);
}
/**
* Not supported.
*/
public function toHavePublicMethods(): void
{
throw InvalidExpectation::fromMethods(['toHavePublicMethods']);
}
/**
* Not supported.
*/
public function toHaveProtectedMethodsBesides(): void
{
throw InvalidExpectation::fromMethods(['toHaveProtectedMethodsBesides']);
}
/**
* Not supported.
*/
public function toHaveProtectedMethods(): void
{
throw InvalidExpectation::fromMethods(['toHaveProtectedMethods']);
}
/**
* Not supported.
*/
public function toHavePrivateMethodsBesides(): void
{
throw InvalidExpectation::fromMethods(['toHavePrivateMethodsBesides']);
}
/**
* Not supported.
*/
public function toHavePrivateMethods(): void
{
throw InvalidExpectation::fromMethods(['toHavePrivateMethods']);
}
/**
* Asserts that the given expectation target is cased correctly.
*/
public function toBeCasedCorrectly(): ArchExpectation
{
return Targeted::make(
$this,
function (ObjectDescription $object): bool {
if (! isset($object->reflectionClass)) {
return false;
}
$realPath = realpath($object->path);
if ($realPath === false) {
return false;
}
foreach (Composer::allNamespacesWithDirectories() as $directory => $namespace) {
if (str_starts_with($realPath, $directory)) {
$relativePath = substr($realPath, strlen($directory) + 1);
$relativePath = explode('.', $relativePath)[0];
$classFromPath = $namespace.'\\'.str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
return $classFromPath === $object->reflectionClass->getName();
}
}
return false;
},
'to be cased correctly',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target is enum.
*/
@@ -477,7 +718,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isEnum(),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isEnum(),
'to be enum',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -492,7 +733,7 @@ final class Expectation
}
/**
* Asserts that the given expectation targets is an class.
* Asserts that the given expectation target is a class.
*/
public function toBeClass(): ArchExpectation
{
@@ -519,7 +760,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isInterface(),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isInterface(),
'to be interface',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -535,14 +776,12 @@ final class Expectation
/**
* Asserts that the given expectation target to be subclass of the given class.
*
* @param class-string $class
*/
public function toExtend(string $class): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && ($class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class)),
sprintf("to extend '%s'", $class),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -561,6 +800,58 @@ final class Expectation
);
}
/**
* Asserts that the given expectation target to use the given trait.
*/
public function toUseTrait(string $trait): ArchExpectation
{
return $this->toUseTraits($trait);
}
/**
* Asserts that the given expectation target to use the given traits.
*
* @param array<int, string>|string $traits
*/
public function toUseTraits(array|string $traits): ArchExpectation
{
$traits = is_array($traits) ? $traits : [$traits];
return Targeted::make(
$this,
function (ObjectDescription $object) use ($traits): bool {
foreach ($traits as $trait) {
if (isset($object->reflectionClass) === false) {
return false;
}
$currentClass = $object->reflectionClass;
$usedTraits = [];
do {
$classTraits = $currentClass->getTraits();
foreach ($classTraits as $traitReflection) {
$usedTraits[$traitReflection->getName()] = $traitReflection->getName();
$nestedTraits = $traitReflection->getTraits();
foreach ($nestedTraits as $nestedTrait) {
$usedTraits[$nestedTrait->getName()] = $nestedTrait->getName();
}
}
} while ($currentClass = $currentClass->getParentClass());
if (! array_key_exists($trait, $usedTraits)) {
return false;
}
}
return true;
},
"to use traits '".implode("', '", $traits)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target to not implement any interfaces.
*/
@@ -568,7 +859,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() === [],
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getInterfaceNames() === [],
'to implement nothing',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -577,7 +868,7 @@ final class Expectation
/**
* Asserts that the given expectation target to only implement the given interfaces.
*
* @param array<int, class-string>|class-string $interfaces
* @param array<int, string>|string $interfaces
*/
public function toOnlyImplement(array|string $interfaces): ArchExpectation
{
@@ -585,7 +876,8 @@ final class Expectation
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => count($interfaces) === count($object->reflectionClass->getInterfaceNames())
fn (ObjectDescription $object): bool => isset($object->reflectionClass)
&& (count($interfaces) === count($object->reflectionClass->getInterfaceNames()))
&& array_diff($interfaces, $object->reflectionClass->getInterfaceNames()) === [],
"to only implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
@@ -593,13 +885,13 @@ final class Expectation
}
/**
* Asserts that the given expectation target to have the given suffix.
* Asserts that the given expectation target to have the given prefix.
*/
public function toHavePrefix(string $prefix): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => str_starts_with($object->reflectionClass->getShortName(), $prefix),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_starts_with($object->reflectionClass->getShortName(), $prefix),
"to have prefix '{$prefix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -612,7 +904,7 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => str_ends_with($object->reflectionClass->getName(), $suffix),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_ends_with($object->reflectionClass->getName(), $suffix),
"to have suffix '{$suffix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -621,7 +913,7 @@ final class Expectation
/**
* Asserts that the given expectation target to implement the given interfaces.
*
* @param array<int, class-string>|class-string $interfaces
* @param array<int, string>|string $interfaces
*/
public function toImplement(array|string $interfaces): ArchExpectation
{
@@ -629,15 +921,7 @@ final class Expectation
return Targeted::make(
$this,
function (ObjectDescription $object) use ($interfaces): bool {
foreach ($interfaces as $interface) {
if (! $object->reflectionClass->implementsInterface($interface)) {
return false;
}
}
return true;
},
fn (ObjectDescription $object): bool => array_all($interfaces, fn (string $interface): bool => isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface)),
"to implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -661,7 +945,18 @@ final class Expectation
return ToUseNothing::make($this);
}
public function toBeUsed(): never
/**
* Asserts that the source code of the given expectation target does not include suspicious characters.
*/
public function toHaveSuspiciousCharacters(): ArchExpectation
{
throw InvalidExpectation::fromMethods(['toHaveSuspiciousCharacters']);
}
/**
* Not supported.
*/
public function toBeUsed(): void
{
throw InvalidExpectation::fromMethods(['toBeUsed']);
}
@@ -701,9 +996,182 @@ final class Expectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => $object->reflectionClass->hasMethod('__invoke'),
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod('__invoke'),
'to be invokable',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Asserts that the given expectation is iterable and contains snake_case keys.
*
* @return self<TValue>
*/
public function toHaveSnakeCaseKeys(string $message = ''): self
{
if (! is_iterable($this->value)) {
InvalidExpectationValue::expected('iterable');
}
foreach ($this->value as $k => $item) {
if (is_string($k)) {
$this->and($k)->toBeSnakeCase($message);
}
if (is_array($item)) {
$this->and($item)->toHaveSnakeCaseKeys($message);
}
}
return $this;
}
/**
* Asserts that the given expectation is iterable and contains kebab-case keys.
*
* @return self<TValue>
*/
public function toHaveKebabCaseKeys(string $message = ''): self
{
if (! is_iterable($this->value)) {
InvalidExpectationValue::expected('iterable');
}
foreach ($this->value as $k => $item) {
if (is_string($k)) {
$this->and($k)->toBeKebabCase($message);
}
if (is_array($item)) {
$this->and($item)->toHaveKebabCaseKeys($message);
}
}
return $this;
}
/**
* Asserts that the given expectation is iterable and contains camelCase keys.
*
* @return self<TValue>
*/
public function toHaveCamelCaseKeys(string $message = ''): self
{
if (! is_iterable($this->value)) {
InvalidExpectationValue::expected('iterable');
}
foreach ($this->value as $k => $item) {
if (is_string($k)) {
$this->and($k)->toBeCamelCase($message);
}
if (is_array($item)) {
$this->and($item)->toHaveCamelCaseKeys($message);
}
}
return $this;
}
/**
* Asserts that the given expectation is iterable and contains StudlyCase keys.
*
* @return self<TValue>
*/
public function toHaveStudlyCaseKeys(string $message = ''): self
{
if (! is_iterable($this->value)) {
InvalidExpectationValue::expected('iterable');
}
foreach ($this->value as $k => $item) {
if (is_string($k)) {
$this->and($k)->toBeStudlyCase($message);
}
if (is_array($item)) {
$this->and($item)->toHaveStudlyCaseKeys($message);
}
}
return $this;
}
/**
* Asserts that the given expectation target to have the given attribute.
*/
public function toHaveAttribute(string $attribute): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getAttributes($attribute) !== [],
"to have attribute '{$attribute}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target has a constructor method.
*/
public function toHaveConstructor(): ArchExpectation
{
return $this->toHaveMethod('__construct');
}
/**
* Asserts that the given expectation target has a destructor method.
*/
public function toHaveDestructor(): ArchExpectation
{
return $this->toHaveMethod('__destruct');
}
/**
* Asserts that the given expectation target is a backed enum of given type.
*/
private function toBeBackedEnum(string $backingType): ArchExpectation
{
return Targeted::make(
$this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass)
&& $object->reflectionClass->isEnum()
&& new ReflectionEnum($object->name)->isBacked() // @phpstan-ignore-line
&& (string) new ReflectionEnum($object->name)->getBackingType() === $backingType, // @phpstan-ignore-line
'to be '.$backingType.' backed enum',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation targets are string backed enums.
*/
public function toBeStringBackedEnums(): ArchExpectation
{
return $this->toBeStringBackedEnum();
}
/**
* Asserts that the given expectation targets are int backed enums.
*/
public function toBeIntBackedEnums(): ArchExpectation
{
return $this->toBeIntBackedEnum();
}
/**
* Asserts that the given expectation target is a string backed enum.
*/
public function toBeStringBackedEnum(): ArchExpectation
{
return $this->toBeBackedEnum('string');
}
/**
* Asserts that the given expectation target is an int backed enum.
*/
public function toBeIntBackedEnum(): ArchExpectation
{
return $this->toBeBackedEnum('int');
}
}
+6 -4
View File
@@ -4,9 +4,10 @@ declare(strict_types=1);
namespace Pest\Expectations;
use function expect;
use Pest\Expectation;
use function expect;
/**
* @internal
*
@@ -16,6 +17,9 @@ use Pest\Expectation;
*/
final class EachExpectation
{
/**
* Indicates if the expectation is the opposite.
*/
private bool $opposite = false;
/**
@@ -23,9 +27,7 @@ final class EachExpectation
*
* @param Expectation<TValue> $original
*/
public function __construct(private readonly Expectation $original)
{
}
public function __construct(private readonly Expectation $original) {}
/**
* Creates a new expectation.
@@ -25,8 +25,14 @@ final class HigherOrderExpectation
*/
private Expectation|EachExpectation $expectation;
/**
* Indicates if the expectation is the opposite.
*/
private bool $opposite = false;
/**
* Indicates if the expectation should reset the value.
*/
private bool $shouldReset = false;
/**
+496 -64
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Pest\Expectations;
use Attribute;
use Pest\Arch\Contracts\ArchExpectation;
use Pest\Arch\Expectations\Targeted;
use Pest\Arch\Expectations\ToBeUsedIn;
@@ -14,12 +15,18 @@ use Pest\Arch\PendingArchExpectation;
use Pest\Arch\SingleArchExpectation;
use Pest\Arch\Support\FileLineFinder;
use Pest\Exceptions\InvalidExpectation;
use Pest\Exceptions\MissingDependency;
use Pest\Expectation;
use Pest\Support\Arr;
use Pest\Support\Exporter;
use Pest\Support\Reflection;
use PHPUnit\Architecture\Elements\ObjectDescription;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExpectationFailedException;
use ReflectionMethod;
use ReflectionProperty;
use Spoofchecker;
use stdClass;
/**
* @internal
@@ -28,16 +35,14 @@ use PHPUnit\Framework\ExpectationFailedException;
*
* @mixin Expectation<TValue>
*/
final class OppositeExpectation
final readonly class OppositeExpectation
{
/**
* Creates a new opposite expectation.
*
* @param Expectation<TValue> $original
*/
public function __construct(private readonly Expectation $original)
{
}
public function __construct(private Expectation $original) {}
/**
* Asserts that the value array not has the provided $keys.
@@ -71,32 +76,126 @@ final class OppositeExpectation
*/
public function toUse(array|string $targets): ArchExpectation
{
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($this->original, $target)->opposite(
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return GroupArchExpectation::fromExpectations($original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($original, $target)->opposite(
fn () => $this->throwExpectationFailedException('toUse', $target),
), is_string($targets) ? [$targets] : $targets));
}
/**
* Asserts that the given expectation target does not have the given permissions
*/
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => substr(sprintf('%o', fileperms($object->path)), -4) !== $permissions,
sprintf('permissions not to be [%s]', $permissions),
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
/**
* Not supported.
*/
public function toHaveLineCountLessThan(): ArchExpectation
{
throw InvalidExpectation::fromMethods(['not', 'toHaveLineCountLessThan']);
}
/**
* Not supported.
*/
public function toHaveMethodsDocumented(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter(
Reflection::getMethodsFromReflectionClass($object->reflectionClass),
fn (ReflectionMethod $method): bool => (enum_exists($object->name) === false || in_array($method->name, ['from', 'tryFrom', 'cases'], true) === false)
&& realpath($method->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $method->getDocComment() !== false,
) === [],
'to have methods without documentation / annotations',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Not supported.
*/
public function toHavePropertiesDocumented(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter(
Reflection::getPropertiesFromReflectionClass($object->reflectionClass),
fn (ReflectionProperty $property): bool => (enum_exists($object->name) === false || in_array($property->name, ['value', 'name'], true) === false)
&& realpath($property->getDeclaringClass()->getFileName() ?: '/') === realpath($object->path) // @phpstan-ignore-line
&& $property->isPromoted() === false
&& $property->getDocComment() !== false,
) === [],
'to have properties without documentation / annotations',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Asserts that the given expectation target does not use the "declare(strict_types=1)" declaration.
*/
public function toUseStrictTypes(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), 'declare(strict_types=1);'),
$original,
fn (ObjectDescription $object): bool => ! (bool) preg_match('/^<\?php\s+declare\(.*?strict_types\s?=\s?1.*?\);/', (string) file_get_contents($object->path)),
'not to use strict types',
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
);
}
/**
* Asserts that the given expectation target does not use the strict equality operator.
*/
public function toUseStrictEquality(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), ' === ') && ! str_contains((string) file_get_contents($object->path), ' !== '),
'to use strict equality',
FileLineFinder::where(fn (string $line): bool => str_contains($line, ' === ') || str_contains($line, ' !== ')),
);
}
/**
* Asserts that the given expectation target is not final.
*/
public function toBeFinal(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && ! $object->reflectionClass->isFinal(),
$original,
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && (isset($object->reflectionClass) === false || ! $object->reflectionClass->isFinal()),
'not to be final',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -107,9 +206,12 @@ final class OppositeExpectation
*/
public function toBeReadonly(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && ! $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line
$original,
fn (ObjectDescription $object): bool => ! enum_exists($object->name) && (isset($object->reflectionClass) === false || ! $object->reflectionClass->isReadOnly()) && assert(true), // @phpstan-ignore-line
'not to be readonly',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -120,9 +222,12 @@ final class OppositeExpectation
*/
public function toBeTrait(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isTrait(),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isTrait(),
'not to be trait',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -141,22 +246,221 @@ final class OppositeExpectation
*/
public function toBeAbstract(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isAbstract(),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isAbstract(),
'not to be abstract',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target does not have a specific method.
*
* @param array<int, string>|string $method
*/
public function toHaveMethod(array|string $method): ArchExpectation
{
$methods = is_array($method) ? $method : [$method];
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => array_filter(
$methods,
fn (string $method): bool => isset($object->reflectionClass) === false || $object->reflectionClass->hasMethod($method),
) === [],
'to not have methods: '.implode(', ', $methods),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target does not have suspicious characters.
*/
public function toHaveSuspiciousCharacters(): ArchExpectation
{
if (! class_exists(Spoofchecker::class)) {
throw new MissingDependency(__FUNCTION__, 'ext-intl >= 2.0');
}
$checker = new Spoofchecker;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => ! $checker->isSuspicious((string) file_get_contents($object->path)),
'to not include suspicious characters',
FileLineFinder::where(fn (string $line): bool => $checker->isSuspicious($line)),
);
}
/**
* Asserts that the given expectation target does not have the given methods.
*
* @param array<int, string> $methods
*/
public function toHaveMethods(array $methods): ArchExpectation
{
return $this->toHaveMethod($methods);
}
/**
* Asserts that the given expectation target not to have the public methods besides the given methods.
*
* @param array<int, string>|string $methods
*/
public function toHavePublicMethodsBesides(array|string $methods): ArchExpectation
{
$methods = is_array($methods) ? $methods : [$methods];
$state = new stdClass;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
function (ObjectDescription $object) use ($methods, &$state): bool {
$reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PUBLIC)
: [];
foreach ($reflectionMethods as $reflectionMethod) {
if (! in_array($reflectionMethod->name, $methods, true)) {
$state->contains = 'public function '.$reflectionMethod->name;
return false;
}
}
return true;
},
$methods === []
? 'not to have public methods'
: sprintf("not to have public methods besides '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
);
}
/**
* Asserts that the given expectation target not to have the public methods.
*/
public function toHavePublicMethods(): ArchExpectation
{
return $this->toHavePublicMethodsBesides([]);
}
/**
* Asserts that the given expectation target not to have the protected methods besides the given methods.
*
* @param array<int, string>|string $methods
*/
public function toHaveProtectedMethodsBesides(array|string $methods): ArchExpectation
{
$methods = is_array($methods) ? $methods : [$methods];
$state = new stdClass;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
function (ObjectDescription $object) use ($methods, &$state): bool {
$reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PROTECTED)
: [];
foreach ($reflectionMethods as $reflectionMethod) {
if (! in_array($reflectionMethod->name, $methods, true)) {
$state->contains = 'protected function '.$reflectionMethod->name;
return false;
}
}
return true;
},
$methods === []
? 'not to have protected methods'
: sprintf("not to have protected methods besides '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
);
}
/**
* Asserts that the given expectation target not to have the protected methods.
*/
public function toHaveProtectedMethods(): ArchExpectation
{
return $this->toHaveProtectedMethodsBesides([]);
}
/**
* Asserts that the given expectation target not to have the private methods besides the given methods.
*
* @param array<int, string>|string $methods
*/
public function toHavePrivateMethodsBesides(array|string $methods): ArchExpectation
{
$methods = is_array($methods) ? $methods : [$methods];
$state = new stdClass;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
function (ObjectDescription $object) use ($methods, &$state): bool {
$reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PRIVATE)
: [];
foreach ($reflectionMethods as $reflectionMethod) {
if (! in_array($reflectionMethod->name, $methods, true)) {
$state->contains = 'private function '.$reflectionMethod->name;
return false;
}
}
return true;
},
$methods === []
? 'not to have private methods'
: sprintf("not to have private methods besides '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
);
}
/**
* Asserts that the given expectation target not to have the private methods.
*/
public function toHavePrivateMethods(): ArchExpectation
{
return $this->toHavePrivateMethodsBesides([]);
}
/**
* Asserts that the given expectation target is not enum.
*/
public function toBeEnum(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum(),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isEnum(),
'not to be enum',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -171,12 +475,15 @@ final class OppositeExpectation
}
/**
* Asserts that the given expectation targets is an class.
* Asserts that the given expectation targets is not class.
*/
public function toBeClass(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
$original,
fn (ObjectDescription $object): bool => ! class_exists($object->name),
'not to be class',
FileLineFinder::where(fn (string $line): bool => true),
@@ -196,9 +503,12 @@ final class OppositeExpectation
*/
public function toBeInterface(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isInterface(),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isInterface(),
'not to be interface',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -213,15 +523,16 @@ final class OppositeExpectation
}
/**
* Asserts that the given expectation target to be subclass of the given class.
*
* @param class-string $class
* Asserts that the given expectation target to be not subclass of the given class.
*/
public function toExtend(string $class): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isSubclassOf($class),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isSubclassOf($class),
sprintf("not to extend '%s'", $class),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -232,34 +543,60 @@ final class OppositeExpectation
*/
public function toExtendNothing(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => $object->reflectionClass->getParentClass() !== false,
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getParentClass() !== false,
'to extend a class',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target not to use the given trait.
*/
public function toUseTrait(string $trait): ArchExpectation
{
return $this->toUseTraits($trait);
}
/**
* Asserts that the given expectation target not to use the given traits.
*
* @param array<int, string>|string $traits
*/
public function toUseTraits(array|string $traits): ArchExpectation
{
$traits = is_array($traits) ? $traits : [$traits];
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => array_all($traits, fn (string $trait): bool => ! isset($object->reflectionClass) || ! in_array($trait, $object->reflectionClass->getTraitNames(), true)),
"not to use traits '".implode("', '", $traits)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation target not to implement the given interfaces.
*
* @param array<int, class-string>|string $interfaces
* @param array<int, string>|string $interfaces
*/
public function toImplement(array|string $interfaces): ArchExpectation
{
$interfaces = is_array($interfaces) ? $interfaces : [$interfaces];
return Targeted::make(
$this->original,
function (ObjectDescription $object) use ($interfaces): bool {
foreach ($interfaces as $interface) {
if ($object->reflectionClass->implementsInterface($interface)) {
return false;
}
}
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return true;
},
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => array_all($interfaces, fn (string $interface): bool => ! isset($object->reflectionClass) || ! $object->reflectionClass->implementsInterface($interface)),
"not to implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -270,9 +607,12 @@ final class OppositeExpectation
*/
public function toImplementNothing(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() !== [],
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getInterfaceNames() !== [],
'to implement an interface',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -280,35 +620,39 @@ final class OppositeExpectation
/**
* Not supported.
*
* @param array<int, class-string>|string $interfaces
*/
public function toOnlyImplement(array|string $interfaces): never
public function toOnlyImplement(): void
{
throw InvalidExpectation::fromMethods(['not', 'toOnlyImplement']);
}
/**
* Not supported.
* Asserts that the given expectation target to not have the given prefix.
*/
public function toHavePrefix(string $prefix): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! str_starts_with($object->reflectionClass->getShortName(), $prefix),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_starts_with($object->reflectionClass->getShortName(), $prefix),
"not to have prefix '{$prefix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Not supported.
* Asserts that the given expectation target to not have the given suffix.
*/
public function toHaveSuffix(string $suffix): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! str_ends_with($object->reflectionClass->getName(), $suffix),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_ends_with($object->reflectionClass->getName(), $suffix),
"not to have suffix '{$suffix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
@@ -316,10 +660,8 @@ final class OppositeExpectation
/**
* Not supported.
*
* @param array<int, string>|string $targets
*/
public function toOnlyUse(array|string $targets): never
public function toOnlyUse(): void
{
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
}
@@ -327,7 +669,7 @@ final class OppositeExpectation
/**
* Not supported.
*/
public function toUseNothing(): never
public function toUseNothing(): void
{
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
}
@@ -337,7 +679,10 @@ final class OppositeExpectation
*/
public function toBeUsed(): ArchExpectation
{
return ToBeUsedInNothing::make($this->original);
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return ToBeUsedInNothing::make($original);
}
/**
@@ -347,12 +692,15 @@ final class OppositeExpectation
*/
public function toBeUsedIn(array|string $targets): ArchExpectation
{
return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedIn::make($this->original, $target)->opposite(
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return GroupArchExpectation::fromExpectations($original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedIn::make($original, $target)->opposite(
fn () => $this->throwExpectationFailedException('toBeUsedIn', $target),
), is_string($targets) ? [$targets] : $targets));
}
public function toOnlyBeUsedIn(): never
public function toOnlyBeUsedIn(): void
{
throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedIn']);
}
@@ -360,7 +708,7 @@ final class OppositeExpectation
/**
* Asserts that the given expectation dependency is not used.
*/
public function toBeUsedInNothing(): never
public function toBeUsedInNothing(): void
{
throw InvalidExpectation::fromMethods(['not', 'toBeUsedInNothing']);
}
@@ -370,14 +718,33 @@ final class OppositeExpectation
*/
public function toBeInvokable(): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$this->original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->hasMethod('__invoke'),
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->hasMethod('__invoke'),
'to not be invokable',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Asserts that the given expectation target not to have the given attribute.
*/
public function toHaveAttribute(string $attribute): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getAttributes($attribute) === [],
"to not have attribute '{$attribute}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
);
}
/**
* Handle dynamic method calls into the original expectation.
*
@@ -431,13 +798,78 @@ final class OppositeExpectation
$exporter = Exporter::default();
$toString = fn (mixed $argument): string => $exporter->shortenedExport($argument);
throw new ExpectationFailedException(sprintf(
'Expecting %s not %s %s.',
$toString($this->original->value),
$exporter->shortenedExport($this->original->value),
strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)),
implode(' ', array_map(fn (mixed $argument): string => $toString($argument), $arguments)),
implode(' ', array_map(fn (mixed $argument): string => $exporter->export($argument), $arguments)),
));
}
/**
* Asserts that the given expectation target does not have a constructor method.
*/
public function toHaveConstructor(): ArchExpectation
{
return $this->toHaveMethod('__construct');
}
/**
* Asserts that the given expectation target does not have a destructor method.
*/
public function toHaveDestructor(): ArchExpectation
{
return $this->toHaveMethod('__destruct');
}
/**
* Asserts that the given expectation target is not a backed enum of given type.
*/
private function toBeBackedEnum(string $backingType): ArchExpectation
{
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make(
$original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| ! $object->reflectionClass->isEnum()
|| ! new \ReflectionEnum($object->name)->isBacked() // @phpstan-ignore-line
|| (string) new \ReflectionEnum($object->name)->getBackingType() !== $backingType, // @phpstan-ignore-line
'not to be '.$backingType.' backed enum',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
);
}
/**
* Asserts that the given expectation targets are not string backed enums.
*/
public function toBeStringBackedEnums(): ArchExpectation
{
return $this->toBeStringBackedEnum();
}
/**
* Asserts that the given expectation targets are not int backed enums.
*/
public function toBeIntBackedEnums(): ArchExpectation
{
return $this->toBeIntBackedEnum();
}
/**
* Asserts that the given expectation target is not a string backed enum.
*/
public function toBeStringBackedEnum(): ArchExpectation
{
return $this->toBeBackedEnum('string');
}
/**
* Asserts that the given expectation target is not an int backed enum.
*/
public function toBeIntBackedEnum(): ArchExpectation
{
return $this->toBeBackedEnum('int');
}
}
@@ -1,27 +0,0 @@
<?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;
}
}
-29
View File
@@ -1,29 +0,0 @@
<?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;
}
}
-26
View File
@@ -1,26 +0,0 @@
<?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;
}
}
-31
View File
@@ -1,31 +0,0 @@
<?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
{
/*
* escapes docblock according to
* https://manual.phpdoc.org/HTMLframesConverter/default/phpDocumentor/tutorial_phpDocumentor.howto.pkg.html#basics.desc
*
* note: '@' escaping is not needed as it cannot be the first character of the line (it always starts with @testdox
*/
assert($method->description !== null);
$methodDescription = str_replace('*/', '{@*}', $method->description);
$annotations[] = "@testdox $methodDescription";
return $annotations;
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Pest\Factories;
/**
* @internal
*/
final class Attribute
{
/**
* @param iterable<int, string> $arguments
*/
public function __construct(public string $name, public iterable $arguments)
{
//
}
}
-27
View File
@@ -1,27 +0,0 @@
<?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;
}
}
-44
View File
@@ -1,44 +0,0 @@
<?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 class 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;
}
}
+3 -3
View File
@@ -28,8 +28,8 @@ trait HigherOrderable
*/
private function bootHigherOrderable(): void
{
$this->chains = new HigherOrderMessageCollection();
$this->factoryProxies = new HigherOrderMessageCollection();
$this->proxies = new HigherOrderMessageCollection();
$this->chains = new HigherOrderMessageCollection;
$this->factoryProxies = new HigherOrderMessageCollection;
$this->proxies = new HigherOrderMessageCollection;
}
}
+1 -3
View File
@@ -9,7 +9,5 @@ namespace Pest\Factories\Covers;
*/
final class CoversClass
{
public function __construct(public string $class)
{
}
public function __construct(public string $class) {}
}
+1 -3
View File
@@ -9,7 +9,5 @@ namespace Pest\Factories\Covers;
*/
final class CoversFunction
{
public function __construct(public string $function)
{
}
public function __construct(public string $function) {}
}
-12
View File
@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Pest\Factories\Covers;
/**
* @internal
*/
final class CoversNothing
{
}
+39 -49
View File
@@ -6,16 +6,18 @@ namespace Pest\Factories;
use ParseError;
use Pest\Concerns;
use Pest\Contracts\AddsAnnotations;
use Pest\Contracts\HasPrintableTestCaseName;
use Pest\Evaluators\Attributes;
use Pest\Exceptions\DatasetMissing;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Exceptions\TestAlreadyExist;
use Pest\Exceptions\TestClosureMustNotBeStatic;
use Pest\Exceptions\TestDescriptionMissing;
use Pest\Factories\Concerns\HigherOrderable;
use Pest\Support\Reflection;
use Pest\Support\Str;
use Pest\TestSuite;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
use RuntimeException;
@@ -26,26 +28,12 @@ final class TestCaseFactory
{
use HigherOrderable;
/**
* The list of annotations.
*
* @var array<int, class-string<AddsAnnotations>>
*/
private const ANNOTATIONS = [
Annotations\Depends::class,
Annotations\Groups::class,
Annotations\CoversNothing::class,
Annotations\TestDox::class,
];
/**
* The list of attributes.
*
* @var array<int, class-string<\Pest\Factories\Attributes\Attribute>>
* @var array<int, Attribute>
*/
private const ATTRIBUTES = [
Attributes\Covers::class,
];
public array $attributes = [];
/**
* The FQN of the Test Case class.
@@ -71,6 +59,11 @@ final class TestCaseFactory
Concerns\Expectable::class,
];
/**
* The namespace for the test case, overrides the path-based namespace when set.
*/
public ?string $namespace = null;
/**
* Creates a new Factory instance.
*/
@@ -101,7 +94,8 @@ final class TestCaseFactory
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn (array $match): string => strtolower($match['drive']), $filename);
}
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
$realpath = (string) realpath($filename);
$filename = str_replace('\\\\', '\\', addslashes($realpath));
$rootPath = TestSuite::getInstance()->rootPath;
$relativePath = str_replace($rootPath.DIRECTORY_SEPARATOR, '', $filename);
@@ -123,8 +117,8 @@ final class TestCaseFactory
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
// Remove escaped quote sequences (maintain namespace)
$relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath);
// Limit to A-Z, a-z, 0-9, '_', '-'.
$relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
// Limit to Unicode letters and numbers.
$relativePath = (string) preg_replace('/[^\p{L}\p{N}\\\\]/u', '', $relativePath);
$classFQN = 'P\\'.$relativePath;
@@ -139,39 +133,30 @@ final class TestCaseFactory
$partsFQN = explode('\\', $classFQN);
$className = array_pop($partsFQN);
$namespace = implode('\\', $partsFQN);
$namespace = $this->namespace ?? implode('\\', $partsFQN);
$baseClass = sprintf('\%s', $this->class);
if ('' === trim($className)) {
if (trim($className) === '') {
$className = 'InvalidTestName'.Str::random();
}
$classAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => $attribute::$above);
$methodAvailableAttributes = array_filter(self::ATTRIBUTES, fn (string $attribute): bool => ! $attribute::$above);
$this->attributes = [
new Attribute(
TestDox::class,
[$this->filename],
),
...$this->attributes,
];
$classAttributes = [];
$attributesCode = Attributes::code($this->attributes);
foreach ($classAvailableAttributes as $attribute) {
$classAttributes = array_reduce(
$methods,
fn (array $carry, TestCaseMethodFactory $methodFactory): array => (new $attribute())->__invoke($methodFactory, $carry),
$classAttributes
);
}
$filenameLiteral = var_export($realpath, true);
$methodsCode = implode('', array_map(
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(
self::ANNOTATIONS,
$methodAvailableAttributes
),
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(),
$methods
));
$classAttributesCode = implode('', array_map(
static fn (string $attribute): string => sprintf("\n%s", $attribute),
array_unique($classAttributes),
));
try {
$classCode = <<<PHP
namespace $namespace;
@@ -179,21 +164,18 @@ final class TestCaseFactory
use Pest\Repositories\DatasetsRepository as __PestDatasets;
use Pest\TestSuite as __PestTestSuite;
/**
* @testdox $filename
*/
$classAttributesCode
$attributesCode
#[\AllowDynamicProperties]
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
$traitsCode
private static \$__filename = '$filename';
public static \$__filename = $filenameLiteral;
$methodsCode
}
PHP;
eval($classCode); // @phpstan-ignore-line
eval($classCode);
} catch (ParseError $caught) {
throw new RuntimeException(sprintf(
"Unable to create test case for test file at %s. \n %s",
@@ -216,6 +198,14 @@ final class TestCaseFactory
throw new TestAlreadyExist($method->filename, $method->description);
}
if (
$method->closure instanceof \Closure &&
new \ReflectionFunction($method->closure)->isStatic()
) {
throw new TestClosureMustNotBeStatic($method);
}
if (! $method->receivesArguments()) {
if (! $method->closure instanceof \Closure) {
throw ShouldNotHappen::fromMessage('The test closure may not be empty.');
@@ -241,7 +231,7 @@ final class TestCaseFactory
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
}
if (Str::evaluable($method->description) === $methodName) {
if ($methodName === Str::evaluable($method->description)) {
return true;
}
}
@@ -259,7 +249,7 @@ final class TestCaseFactory
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
}
if (Str::evaluable($method->description) === $methodName) {
if ($methodName === Str::evaluable($method->description)) {
return $method;
}
}
+111 -44
View File
@@ -5,13 +5,18 @@ declare(strict_types=1);
namespace Pest\Factories;
use Closure;
use Pest\Contracts\AddsAnnotations;
use Pest\Evaluators\Attributes;
use Pest\Exceptions\ShouldNotHappen;
use Pest\Factories\Concerns\HigherOrderable;
use Pest\Repositories\DatasetsRepository;
use Pest\Support\Description;
use Pest\Support\Str;
use Pest\TestSuite;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
/**
@@ -22,20 +27,67 @@ final class TestCaseMethodFactory
use HigherOrderable;
/**
* The test's describing, if any.
* The list of attributes.
*
* @var array<int, Attribute>
*/
public ?string $describing = null;
public array $attributes = [];
/**
* The test's describing, if any.
*
* @var array<int, Description>
*/
public array $describing = [];
/**
* The test's description, if any.
*/
public ?string $description = null;
/**
* The test's number of repetitions.
*/
public int $repetitions = 1;
/**
* The test's number of flaky retry tries.
*/
public ?int $flakyTries = null;
/**
* Determines if the test is a "todo".
*/
public bool $todo = false;
/**
* The associated issue numbers.
*
* @var array<int, int>
*/
public array $issues = [];
/**
* The test assignees.
*
* @var array<int, string>
*/
public array $assignees = [];
/**
* The associated PRs numbers.
*
* @var array<int, int>
*/
public array $prs = [];
/**
* The test's notes.
*
* @var array<int, string>
*/
public array $notes = [];
/**
* The test's datasets.
*
@@ -58,31 +110,28 @@ final class TestCaseMethodFactory
public array $groups = [];
/**
* The covered classes and functions.
*
* @var array<int, \Pest\Factories\Covers\CoversClass|\Pest\Factories\Covers\CoversFunction|\Pest\Factories\Covers\CoversNothing>
* @see This property is not actually used in the codebase, it's only here to make Rector happy.
*/
public array $covers = [];
public bool $__ran = false;
/**
* Creates a new test case method 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
(Assert::getCount() > 0 || $this->doesNotPerformAssertions()) ?: self::markTestIncomplete(); // @phpstan-ignore-line
};
$this->bootHigherOrderable();
}
/**
* Creates the test's closure.
* Sets the test's hooks, and runs any proxy to the test case.
*/
public function getClosure(TestCase $concrete): Closure
public function setUp(TestCase $concrete): void
{
$concrete::flush(); // @phpstan-ignore-line
@@ -90,16 +139,32 @@ final class TestCaseMethodFactory
throw ShouldNotHappen::fromMessage('Description can not be empty.');
}
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename);
assert($testCase instanceof TestCaseFactory);
$testCase->factoryProxies->proxy($concrete);
$this->factoryProxies->proxy($concrete);
}
/**
* Flushes the test case.
*/
public function tearDown(TestCase $concrete): void
{
$concrete::flush(); // @phpstan-ignore-line
}
/**
* Creates the test's closure.
*/
public function getClosure(): Closure
{
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename);
assert($testCase instanceof TestCaseFactory);
$method = $this;
return function () use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line
return function (...$arguments) use ($testCase, $method, $closure): mixed {
/* @var TestCase $this */
$testCase->proxies->proxy($this);
$method->proxies->proxy($this);
@@ -107,7 +172,9 @@ final class TestCaseMethodFactory
$testCase->chains->chain($this);
$method->chains->chain($this);
return \Pest\Support\Closure::bind($closure, $this, self::class)(...func_get_args());
$this->__ran = true;
return \Pest\Support\Closure::bind($closure, $this, self::class)(...$arguments);
};
}
@@ -116,16 +183,13 @@ final class TestCaseMethodFactory
*/
public function receivesArguments(): bool
{
return $this->datasets !== [] || $this->depends !== [];
return $this->datasets !== [] || $this->depends !== [] || $this->repetitions > 1;
}
/**
* 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
public function buildForEvaluation(): string
{
if ($this->description === null) {
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
@@ -134,43 +198,46 @@ final class TestCaseMethodFactory
$methodName = Str::evaluable($this->description);
$datasetsCode = '';
$annotations = ['@test'];
$attributes = [];
foreach ($annotationsToUse as $annotation) {
$annotations = (new $annotation())->__invoke($this, $annotations);
}
$this->attributes = [
new Attribute(
Test::class,
[],
),
new Attribute(
TestDox::class,
[str_replace('*/', '{@*}', $this->description)],
),
...$this->attributes,
];
foreach ($attributesToUse as $attribute) {
$attributes = (new $attribute())->__invoke($this, $attributes);
foreach ($this->depends as $depend) {
$depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend));
$this->attributes[] = new Attribute(
Depends::class,
[$depend],
);
}
if ($this->datasets !== [] || $this->repetitions > 1) {
$dataProviderName = $methodName.'_dataset';
$annotations[] = "@dataProvider $dataProviderName";
$this->attributes[] = new Attribute(
DataProvider::class,
[$dataProviderName],
);
$datasetsCode = $this->buildDatasetForEvaluation($methodName, $dataProviderName);
}
$annotations = implode('', array_map(
static fn (string $annotation): string => sprintf("\n * %s", $annotation), $annotations,
));
$attributes = implode('', array_map(
static fn (string $attribute): string => sprintf("\n %s", $attribute), $attributes,
));
$attributesCode = Attributes::code($this->attributes);
return <<<PHP
/**$annotations
*/
$attributes
public function $methodName()
$attributesCode
public function $methodName(...\$arguments)
{
\$test = \Pest\TestSuite::getInstance()->tests->get(self::\$__filename)->getMethod(\$this->name())->getClosure(\$this);
return \$this->__runTest(
\$test,
...func_get_args(),
\$this->__test,
...\$arguments,
);
}
$datasetsCode
+152 -34
View File
@@ -2,10 +2,15 @@
declare(strict_types=1);
use Pest\Concerns\Expectable;
use Pest\Browser\Api\ArrayablePendingAwaitablePage;
use Pest\Browser\Api\PendingAwaitablePage;
use Pest\Configuration;
use Pest\Exceptions\AfterAllWithinDescribe;
use Pest\Exceptions\BeforeAllWithinDescribe;
use Pest\Expectation;
use Pest\Installers\PluginBrowser;
use Pest\Mutate\Contracts\MutationTestRunner;
use Pest\Mutate\Repositories\ConfigurationRepository;
use Pest\PendingCalls\AfterEachCall;
use Pest\PendingCalls\BeforeEachCall;
use Pest\PendingCalls\DescribeCall;
@@ -13,7 +18,9 @@ use Pest\PendingCalls\TestCall;
use Pest\PendingCalls\UsesCall;
use Pest\Repositories\DatasetsRepository;
use Pest\Support\Backtrace;
use Pest\Support\Container;
use Pest\Support\DatasetInfo;
use Pest\Support\Description;
use Pest\Support\HigherOrderTapProxy;
use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
@@ -39,8 +46,8 @@ if (! function_exists('beforeAll')) {
*/
function beforeAll(Closure $closure): void
{
if (! is_null(DescribeCall::describing())) {
$filename = Backtrace::file();
if (DescribeCall::describing() !== []) {
$filename = Backtrace::testFile();
throw new BeforeAllWithinDescribe($filename);
}
@@ -53,11 +60,11 @@ if (! function_exists('beforeEach')) {
/**
* Runs the given closure before each test in the current file.
*
* @return HigherOrderTapProxy<Expectable|TestCall|TestCase>|Expectable|TestCall|TestCase|mixed
* @param-closure-this TestCall $closure
*/
function beforeEach(Closure $closure = null): BeforeEachCall
function beforeEach(?Closure $closure = null): BeforeEachCall
{
$filename = Backtrace::file();
$filename = Backtrace::testFile();
return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
}
@@ -82,14 +89,12 @@ if (! function_exists('describe')) {
* Adds the given closure as a group of tests. The first argument
* is the group description; the second argument is a closure
* that contains the group tests.
*
* @return HigherOrderTapProxy<Expectable|TestCall|TestCase>|Expectable|TestCall|TestCase|mixed
*/
function describe(string $description, Closure $tests): DescribeCall
{
$filename = Backtrace::testFile();
return new DescribeCall(TestSuite::getInstance(), $filename, $description, $tests);
return new DescribeCall(TestSuite::getInstance(), $filename, new Description($description), $tests);
}
}
@@ -102,23 +107,35 @@ if (! function_exists('uses')) {
*/
function uses(string ...$classAndTraits): UsesCall
{
$filename = Backtrace::file();
$filename = Backtrace::testFile();
return new UsesCall($filename, array_values($classAndTraits));
}
}
if (! function_exists('pest')) {
/**
* Creates a new Pest configuration instance.
*/
function pest(): Configuration
{
return new Configuration(Backtrace::testFile());
}
}
if (! function_exists('test')) {
/**
* Adds the given closure as a test. The first argument
* is the test description; the second argument is
* a closure that contains the test expectations.
*
* @return Expectable|TestCall|TestCase|mixed
* @param-closure-this TestCall $closure
*
* @return ($description is string ? TestCall : HigherOrderTapProxy|TestCall)
*/
function test(string $description = null, Closure $closure = null): HigherOrderTapProxy|TestCall
function test(?string $description = null, ?Closure $closure = null): HigherOrderTapProxy|TestCall
{
if ($description === null && TestSuite::getInstance()->test instanceof \PHPUnit\Framework\TestCase) {
if ($description === null && TestSuite::getInstance()->test instanceof TestCase) {
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
}
@@ -134,34 +151,23 @@ if (! function_exists('it')) {
* is the test description; the second argument is
* a closure that contains the test expectations.
*
* @return Expectable|TestCall|TestCase|mixed
* @param-closure-this TestCall $closure
*/
function it(string $description, Closure $closure = null): TestCall
function it(string $description, ?Closure $closure = null): TestCall
{
$description = sprintf('it %s', $description);
/** @var TestCall $test */
$test = test($description, $closure);
return $test;
return test($description, $closure);
}
}
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 Expectable|TestCall|TestCase|mixed
* Creates a new test that is marked as "todo".
*/
function todo(string $description): TestCall
{
$test = test($description);
assert($test instanceof TestCall);
return $test->todo();
return test($description)->todo();
}
}
@@ -169,11 +175,11 @@ if (! function_exists('afterEach')) {
/**
* Runs the given closure after each test in the current file.
*
* @return Expectable|HigherOrderTapProxy<Expectable|TestCall|TestCase>|TestCall|mixed
* @param-closure-this TestCall $closure
*/
function afterEach(Closure $closure = null): AfterEachCall
function afterEach(?Closure $closure = null): AfterEachCall
{
$filename = Backtrace::file();
$filename = Backtrace::testFile();
return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);
}
@@ -185,8 +191,8 @@ if (! function_exists('afterAll')) {
*/
function afterAll(Closure $closure): void
{
if (! is_null(DescribeCall::describing())) {
$filename = Backtrace::file();
if (DescribeCall::describing() !== []) {
$filename = Backtrace::testFile();
throw new AfterAllWithinDescribe($filename);
}
@@ -194,3 +200,115 @@ if (! function_exists('afterAll')) {
TestSuite::getInstance()->afterAll->set($closure);
}
}
if (! function_exists('covers')) {
/**
* Specifies which classes, or functions, a test case covers.
*
* @param array<int, string>|string $classesOrFunctions
*/
function covers(array|string ...$classesOrFunctions): void
{
$filename = Backtrace::testFile();
$beforeEachCall = (new BeforeEachCall(TestSuite::getInstance(), $filename));
$beforeEachCall->covers(...$classesOrFunctions);
$beforeEachCall->group('__pest_mutate_only');
/** @var MutationTestRunner $runner */
$runner = Container::getInstance()->get(MutationTestRunner::class);
/** @var ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;
$paths = $configurationRepository->cliConfiguration->toArray()['paths'] ?? false;
if ($runner->isEnabled() && ! $everything && ! is_array($classes) && ! is_array($paths)) {
$beforeEachCall->only('__pest_mutate_only');
}
}
}
if (! function_exists('mutates')) {
/**
* Specifies which classes, enums, or traits a test case mutates.
*
* @param array<int, string>|string $targets
*/
function mutates(array|string ...$targets): void
{
$filename = Backtrace::testFile();
$beforeEachCall = (new BeforeEachCall(TestSuite::getInstance(), $filename));
$beforeEachCall->group('__pest_mutate_only');
/** @var MutationTestRunner $runner */
$runner = Container::getInstance()->get(MutationTestRunner::class);
/** @var ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;
$paths = $configurationRepository->cliConfiguration->toArray()['paths'] ?? false;
if ($runner->isEnabled() && ! $everything && ! is_array($classes) && ! is_array($paths)) {
$beforeEachCall->only('__pest_mutate_only');
}
/** @var ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$paths = $configurationRepository->cliConfiguration->toArray()['paths'] ?? false;
if (! is_array($paths)) {
$configurationRepository->globalConfiguration('default')->class(...$targets); // @phpstan-ignore-line
}
}
}
if (! function_exists('fixture')) {
/**
* Returns the absolute path to a fixture file.
*/
function fixture(string $file): string
{
$file = implode(DIRECTORY_SEPARATOR, [
TestSuite::getInstance()->rootPath,
TestSuite::getInstance()->testPath,
'Fixtures',
str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file),
]);
$fileRealPath = realpath($file);
if ($fileRealPath === false) {
throw new InvalidArgumentException(
'The fixture file ['.$file.'] does not exist.',
);
}
return $fileRealPath;
}
}
if (! function_exists('visit')) {
/**
* Browse to the given URL.
*
* @template TUrl of array<int, string>|string
*
* @param TUrl $url
* @param array<string, mixed> $options
* @return (TUrl is array<int, string> ? ArrayablePendingAwaitablePage : PendingAwaitablePage)
*/
function visit(array|string $url, array $options = []): ArrayablePendingAwaitablePage|PendingAwaitablePage
{
if (! class_exists(Pest\Browser\Configuration::class)) {
PluginBrowser::install();
exit(0);
}
// @phpstan-ignore-next-line
return test()->visit($url, $options);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Installers;
use Pest\Support\View;
final readonly class PluginBrowser
{
public static function install(): void
{
View::render('installers/plugin-browser');
}
}
+96 -22
View File
@@ -4,31 +4,45 @@ declare(strict_types=1);
namespace Pest;
use NunoMaduro\Collision\Writer;
use Pest\Contracts\Bootstrapper;
use Pest\Exceptions\FatalException;
use Pest\Exceptions\NoDirtyTestsFound;
use Pest\Plugins\Actions\CallsAddsOutput;
use Pest\Plugins\Actions\CallsBoot;
use Pest\Plugins\Actions\CallsHandleArguments;
use Pest\Plugins\Actions\CallsShutdown;
use Pest\Plugins\Actions\CallsHandleOriginalArguments;
use Pest\Plugins\Actions\CallsTerminable;
use Pest\Support\Container;
use Pest\Support\Reflection;
use Pest\Support\View;
use PHPUnit\TestRunner\TestResult\Facade;
use PHPUnit\TextUI\Application;
use PHPUnit\TextUI\Configuration\Registry;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use Whoops\Exception\Inspector;
/**
* @internal
*/
final class Kernel
{
/**
* Either the kernel is terminated or not.
*/
private bool $terminated = false;
/**
* The Kernel bootstrappers.
*
* @var array<int, class-string>
*/
private const BOOTSTRAPPERS = [
private const array BOOTSTRAPPERS = [
Bootstrappers\BootOverrides::class,
Bootstrappers\BootPhpUnitConfiguration::class,
Plugins\Tia\Bootstrapper::class,
Bootstrappers\BootSubscribers::class,
Bootstrappers\BootFiles::class,
Bootstrappers\BootView::class,
@@ -36,15 +50,22 @@ final class Kernel
Bootstrappers\BootExcludeList::class,
];
/**
* The Kernel restarters — resolved and invoked from `bin/pest`
* before any other Pest class is touched, so the list is exposed
* on the Kernel rather than driven from `bin/pest` directly.
*
* @var array<int, class-string<Contracts\Restarter>>
*/
public const array RESTARTERS = [
Restarters\XdebugRestarter::class,
Restarters\PcovRestarter::class,
];
/**
* Creates a new Kernel instance.
*/
public function __construct(
private readonly Application $application,
private readonly OutputInterface $output,
) {
register_shutdown_function(fn () => $this->shutdown());
}
public function __construct(private readonly Application $application, private readonly OutputInterface $output) {}
/**
* Boots the Kernel.
@@ -59,6 +80,13 @@ final class Kernel
->add(OutputInterface::class, $output)
->add(Container::class, $container);
$kernel = new self(
new Application,
$output,
);
register_shutdown_function($kernel->shutdown(...));
foreach (self::BOOTSTRAPPERS as $bootstrapper) {
$bootstrapper = Container::getInstance()->get($bootstrapper);
assert($bootstrapper instanceof Bootstrapper);
@@ -68,11 +96,6 @@ final class Kernel
CallsBoot::execute();
$kernel = new self(
new Application(),
$output,
);
Container::getInstance()->add(self::class, $kernel);
return $kernel;
@@ -81,14 +104,17 @@ final class Kernel
/**
* Runs the application, and returns the exit code.
*
* @param array<int, string> $args
* @param array<int, string> $originalArguments
* @param array<int, string> $arguments
*/
public function handle(array $args): int
public function handle(array $originalArguments, array $arguments): int
{
$args = CallsHandleArguments::execute($args);
CallsHandleOriginalArguments::execute($originalArguments);
$arguments = CallsHandleArguments::execute($arguments);
try {
$this->application->run($args);
$this->application->run($arguments);
} catch (NoDirtyTestsFound) {
$this->output->writeln([
'',
@@ -100,22 +126,70 @@ final class Kernel
$configuration = Registry::get();
$result = Facade::result();
return CallsAddsOutput::execute(
$result = CallsAddsOutput::execute(
Result::exitCode($configuration, $result),
);
$this->terminate();
return $result;
}
/**
* Shutdown the Kernel.
* Terminate the Kernel.
*/
public function shutdown(): void
public function terminate(): void
{
if ($this->terminated) {
return;
}
$this->terminated = true;
$preBufferOutput = Container::getInstance()->get(KernelDump::class);
assert($preBufferOutput instanceof KernelDump);
$preBufferOutput->shutdown();
$preBufferOutput->terminate();
CallsShutdown::execute();
CallsTerminable::execute();
}
/**
* Shutdowns unexpectedly the Kernel.
*/
public function shutdown(): void
{
$this->terminate();
if (is_array($error = error_get_last())) {
if (! in_array($error['type'], [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR], true)) {
return;
}
$message = $error['message'];
$file = $error['file'];
$line = $error['line'];
try {
$writer = new Writer(null, $this->output);
$throwable = new FatalException($message);
Reflection::setPropertyValue($throwable, 'line', $line);
Reflection::setPropertyValue($throwable, 'file', $file);
$inspector = new Inspector($throwable);
$writer->write($inspector);
} catch (Throwable) { // @phpstan-ignore-line
View::render('components.badge', [
'type' => 'ERROR',
'content' => sprintf('%s in %s:%d', $message, $file, $line),
]);
}
exit(1);
}
}
}
+13 -5
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Pest;
use Laravel\Pao\Execution;
use Pest\Support\View;
use Symfony\Component\Console\Output\OutputInterface;
@@ -28,6 +29,10 @@ final class KernelDump
*/
public function enable(): void
{
if (class_exists(Execution::class) && Execution::running()) {
return;
}
ob_start(function (string $message): string {
$this->buffer .= $message;
@@ -40,7 +45,7 @@ final class KernelDump
*/
public function disable(): void
{
@ob_clean(); // @phpstan-ignore-line
@ob_clean();
if ($this->buffer !== '') {
$this->flush();
@@ -48,9 +53,9 @@ final class KernelDump
}
/**
* Shutdown the output buffering.
* Terminate the output buffering.
*/
public function shutdown(): void
public function terminate(): void
{
$this->disable();
}
@@ -68,6 +73,10 @@ final class KernelDump
$type = 'INFO';
if (is_array($error = error_get_last()) && in_array($error['type'], [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR], true)) {
return;
}
if ($this->isInternalError($this->buffer)) {
$type = 'ERROR';
$this->buffer = str_replace(
@@ -107,7 +116,6 @@ final class KernelDump
*/
private function isInternalError(string $output): bool
{
return str_contains($output, 'An error occurred inside PHPUnit.')
|| str_contains($output, 'Fatal error');
return str_contains($output, 'An error occurred inside PHPUnit.');
}
}

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