diff --git a/CHANGELOG.md b/CHANGELOG.md index d72d6ea5..5b564916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.10.43](https://github.com/php-testo/testo/compare/0.10.42...0.10.43) (2026-08-18) + + +### Documentation + +* **skills:** drop cross-framework comparisons from non-migration skills ([e976692](https://github.com/php-testo/testo/commit/e9766922e9de526b8bb153b92dc83f9b041d32c5)) + ## [0.10.42](https://github.com/php-testo/testo/compare/0.10.41...0.10.42) (2026-08-18) diff --git a/README.md b/README.md index 99c1698c..f5af6637 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ [![Documentation](https://img.shields.io/badge/Documentation-blue?style=for-the-badge&logo=gitbook&logoColor=white)](https://php-testo.github.io) [![Support on Boosty](https://img.shields.io/static/v1?style=for-the-badge&label=&message=Sponsorship&logo=Boosty&logoColor=white&color=%23F15F2C)](https://boosty.to/roxblnfk) -[![Vibe Index](https://img.shields.io/static/v1?label=Vibe+Index&message=3.3&color=22b0b0&style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI%2BPHBhdGggZD0iTTkgNCBROSAxMyAxOCAxMyBROSAxMyA5IDIyIFE5IDEzIDAgMTMgUTkgMTMgOSA0IFoiLz48cGF0aCBkPSJNMTkgMSBRMTkgNiAyNCA2IFExOSA2IDE5IDExIFExOSA2IDE0IDYgUTE5IDYgMTkgMSBaIi8%2BPHBhdGggZD0iTTIwIDE0IFEyMCAxOCAyNCAxOCBRMjAgMTggMjAgMjIgUTIwIDE4IDE2IDE4IFEyMCAxOCAyMCAxNCBaIi8%2BPC9zdmc%2B)](https://github.com/roxblnfk/action-vibe-index) +[![Vibe Index](https://img.shields.io/static/v1?label=Vibe+Index&message=3.4&color=23afb3&style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI%2BPHBhdGggZD0iTTkgNCBROSAxMyAxOCAxMyBROSAxMyA5IDIyIFE5IDEzIDAgMTMgUTkgMTMgOSA0IFoiLz48cGF0aCBkPSJNMTkgMSBRMTkgNiAyNCA2IFExOSA2IDE5IDExIFExOSA2IDE0IDYgUTE5IDYgMTkgMSBaIi8%2BPHBhdGggZD0iTTIwIDE0IFEyMCAxOCAyNCAxOCBRMjAgMTggMjAgMjIgUTIwIDE4IDE2IDE4IFEyMCAxOCAyMCAxNCBaIi8%2BPC9zdmc%2B)](https://github.com/roxblnfk/action-vibe-index) [![Psalm Level](https://shepherd.dev/github/php-testo/testo/level.svg)](https://shepherd.dev/github/php-testo/testo) [![Type Coverage](https://shepherd.dev/github/php-testo/testo/coverage.svg)](https://shepherd.dev/github/php-testo/testo) [![codecov](https://codecov.io/gh/php-testo/testo/branch/1.x/graph/badge.svg)](https://codecov.io/gh/php-testo/testo) diff --git a/composer.json b/composer.json index dc44a8a5..e268099d 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "psr/log": "^2.0 || ^3.0", "symfony/console": "^6.4 || ^7 || ^8.0", "symfony/finder": "^6.4 || ^7 || ^8.0", - "testo/assert": "^0.1.13", + "testo/assert": "^0.1.14", "testo/fiber": "^0.1.2", "testo/bench": "^0.1.8", "testo/bridge-symfony-console": "^0.1.9", diff --git a/plugin/assert/CHANGELOG.md b/plugin/assert/CHANGELOG.md index a0fdb4bb..f3af4e7d 100644 --- a/plugin/assert/CHANGELOG.md +++ b/plugin/assert/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.1.14](https://github.com/php-testo/testo/compare/assert-0.1.13...assert-0.1.14) (2026-08-18) + + +### Documentation + +* **assert:** drop the PHPUnit reference from sameElementsAs and show an example ([5a000e6](https://github.com/php-testo/testo/commit/5a000e6c8e8aa7fe1e64fda49b15199acf53bde0)) + ## [0.1.13](https://github.com/php-testo/testo/compare/assert-0.1.12...assert-0.1.13) (2026-08-18) diff --git a/plugin/assert/composer.json b/plugin/assert/composer.json index f869a678..e0ff127e 100644 --- a/plugin/assert/composer.json +++ b/plugin/assert/composer.json @@ -21,7 +21,7 @@ ], "require": { "php": ">=8.2", - "testo/testo": "0.10.42 - 1" + "testo/testo": "0.10.43 - 1" }, "autoload": { "psr-4": { diff --git a/plugin/assert/src/Api/Builtin/ArrayType.php b/plugin/assert/src/Api/Builtin/ArrayType.php index 26bb8718..fbd8838e 100644 --- a/plugin/assert/src/Api/Builtin/ArrayType.php +++ b/plugin/assert/src/Api/Builtin/ArrayType.php @@ -46,8 +46,13 @@ public function isList(string $message = ''): static; /** * Asserts that the array holds the same elements as the expected iterable, regardless of order. * - * Both sides are canonicalized (recursively sorted, keys discarded) and then compared loosely, - * matching PHPUnit's {@see \PHPUnit\Framework\Assert::assertEqualsCanonicalizing()} semantics. + * Both sides are canonicalized (recursively sorted, keys discarded) and then compared loosely: + * + * ```php + * Assert::array([3, 1, 2])->sameElementsAs([1, 2, 3]); // passes — order ignored + * Assert::array(['a' => 1, 'b' => 2])->sameElementsAs([2, 1]); // passes — keys ignored + * Assert::array([1, 2])->sameElementsAs([1, 2, 3]); // fails — different elements + * ``` * * @param iterable $expected The expected elements, in any order. * @param string $message Optional message for the assertion. diff --git a/plugin/assert/src/Internal/Assertion/AssertArray.php b/plugin/assert/src/Internal/Assertion/AssertArray.php index 1883a343..ce266318 100644 --- a/plugin/assert/src/Internal/Assertion/AssertArray.php +++ b/plugin/assert/src/Internal/Assertion/AssertArray.php @@ -143,8 +143,7 @@ public function sameElementsAs(iterable $expected, string $message = ''): static /** * Recursively sorts an array by value and discards keys, so two arrays holding the same - * elements in any order (and under any keys) canonicalize to an identical shape. Mirrors the - * canonicalization PHPUnit applies for `assertEqualsCanonicalizing`. + * elements in any order (and under any keys) canonicalize to an identical shape. */ private static function canonicalize(array $value): array { diff --git a/resources/version.json b/resources/version.json index 709c2190..a693dc67 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,12 +1,12 @@ { - ".": "0.10.42", + ".": "0.10.43", "plugin/repeat": "0.1.9", "plugin/convention": "0.1.4", "plugin/lifecycle": "0.1.5", "plugin/retry": "0.1.5", "plugin/filter": "0.1.6", "plugin/test": "0.1.6", - "plugin/assert": "0.1.13", + "plugin/assert": "0.1.14", "plugin/data": "0.1.8", "plugin/inline": "0.1.8", "plugin/bench": "0.1.8", diff --git a/skills/testo-flaky-tests/SKILL.md b/skills/testo-flaky-tests/SKILL.md index 22e9757b..0f3336a2 100644 --- a/skills/testo-flaky-tests/SKILL.md +++ b/skills/testo-flaky-tests/SKILL.md @@ -67,7 +67,7 @@ public function __construct( ) {} ``` -- `times` is the **total** number of runs. `#[Repeat(times: 3)]` runs the test three times (mirrors Kotlin's `repeat(n)` and JUnit's `@RepeatedTest(n)`). It is **not** "additional repetitions on top of one run". +- `times` is the **total** number of runs. `#[Repeat(times: 3)]` runs the test three times. It is **not** "additional repetitions on top of one run". - `maxFailures` defaults to `0` — any single failure fails the whole loop. - Combining with `#[Retry]`: Repeat runs *inside* Retry — each retry attempt re-runs the full repeat cycle. Possible, but the semantics are subtle; surface it to the user before suggesting both. @@ -91,7 +91,7 @@ Don't ship `#[Repeat(times: 50)]` long-term on a fast suite — CI cost adds up. ## Pitfalls - **Don't disable `markFlaky`** on `#[Retry]` / `#[Repeat]` (it defaults to `true`). Setting `markFlaky: false` is **silent rot** — a flaky test that retries to green hides the underlying defect. Only flip it off when the user explicitly asks. -- **`Repeat(times: N)` is total runs, not extra runs.** `Repeat(times: 1)` runs the test once. People coming from older PHPUnit `@Repeat` semantics expect "additional" — they're wrong here. +- **`Repeat(times: N)` is total runs, not extra runs.** `Repeat(times: 1)` runs the test once, `Repeat(times: 3)` runs it three times total — `N` is the run count, not a number of *additional* runs on top of the first. - Combining `#[Retry]` with `#[Repeat]` is allowed: Repeat runs **inside** Retry (each retry attempt re-runs the full repeat cycle). Only suggest both when the user genuinely wants that nesting. - A test with `Expect::exception(...)` and `#[Retry]` is almost always wrong — expected exceptions are deterministic by design. - Don't use retries to paper over network calls in unit tests — replace the dependency with a fake instead. diff --git a/skills/testo-write-tests/SKILL.md b/skills/testo-write-tests/SKILL.md index 4edf99dc..95c53dc7 100644 --- a/skills/testo-write-tests/SKILL.md +++ b/skills/testo-write-tests/SKILL.md @@ -5,8 +5,8 @@ description: Write or modify tests in a project that uses the Testo PHP testing # Writing tests with Testo -Testo is **not PHPUnit**. The attribute set, assertion facade, exception expectations, and lifecycle -hooks are Testo's own — do not transliterate PHPUnit idioms. +The attribute set, assertion facade, exception expectations, and lifecycle hooks are Testo's own. +Write them the Testo way described below — don't transliterate idioms from other test frameworks. ## Before you write code @@ -81,7 +81,7 @@ Assert::string($s)->contains('foo')->notContains('bar'); Assert::int($n)->greaterThan(0)->lessThanOrEqual(100); Assert::numeric($n)->between(1, 100); // int, float, or numeric string Assert::array($a)->hasKeys('id', 'name')->isList()->hasCount(3)->contains('x')->notContains('y'); -Assert::array($a)->sameElementsAs([3, 2, 1]); // order-insensitive, like assertEqualsCanonicalizing +Assert::array($a)->sameElementsAs([3, 2, 1]); // order-insensitive, keys ignored Assert::object($o)->instanceOf(Foo::class)->hasProperty('id'); Assert::json($s)->isObject()->hasKeys(['data', 'meta'])->assertPath('$.data.id', 42); ``` @@ -241,7 +241,7 @@ Always pass `--json` (as above) for a compact, machine-readable report that's ch summary plus the failed tests. Use `--log-json=build/report.json` to write it to a file while keeping the terminal output. -Use the Testo CLI, **never** `phpunit`. +Always run tests through the Testo CLI (`vendor/bin/testo`). ## Pitfalls