From beefca2904b75be6f8db571fade487eba5092951 Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 05:11:57 +0500 Subject: [PATCH 1/9] Remove Psalm: remove psalm/phar dependency, psalm.xml, and update scripts --- composer.json | 17 +++++++---------- psalm.xml | 21 --------------------- 2 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 psalm.xml diff --git a/composer.json b/composer.json index 27380c5..257697e 100644 --- a/composer.json +++ b/composer.json @@ -34,15 +34,12 @@ }, "minimum-stability": "stable", "scripts": { - "analyze": [ - "@phpstan", - "@psalm" - ], - "phpstan": "./vendor/bin/phpstan analyse", - "psalm": "./vendor/bin/psalm.phar --config=psalm.xml" + "analyze": [ + "@phpstan" + ], + "phpstan": "./vendor/bin/phpstan analyse" }, - "require-dev": { - "psalm/phar": "^6.14", - "phpstan/phpstan": "^2.1" - } + "require-dev": { + "phpstan/phpstan": "^2.1" + } } diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 2edfffe..0000000 --- a/psalm.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file From 2848baa75a37eca644f396726677099e5f52623e Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 05:15:07 +0500 Subject: [PATCH 2/9] Move ulid method from SpecialAssertions to StringAssertions and update README --- README.md | 5 ++--- src/Traits/SpecialAssertions.php | 17 ----------------- src/Traits/StringAssertions.php | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f579213..2028ca5 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ fact(false)->notFalse(); // Fails ``` -### Comparison and Equality assertions +### Comparison and equality assertions ```php fact(42)->is(42); // Passes fact(42)->is('42'); // Fails due to type difference @@ -131,8 +131,6 @@ fact(15)->isBetween(1, 10); // Fails ### Special assertions ```php -fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID) -fact('invalid-ulid')->ulid(); // Fails ``` ### String assertions @@ -175,6 +173,7 @@ fact('invalid json')->isJson(); // Fails fact('user@example.com')->isValidEmail(); // Passes fact('invalid-email')->isValidEmail(); // Fails + ``` ### Type Checking assertions diff --git a/src/Traits/SpecialAssertions.php b/src/Traits/SpecialAssertions.php index dc33542..3fa43ae 100644 --- a/src/Traits/SpecialAssertions.php +++ b/src/Traits/SpecialAssertions.php @@ -12,21 +12,4 @@ */ trait SpecialAssertions { - /** - * Asserts that a variable is a valid ULID. - * - * This method checks if the actual value matches the ULID (Universally Unique Lexicographically Sortable Identifier) format. - * - * @see https://github.com/ulid/spec - * - * Example usage: - * fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID) - * fact('invalid-ulid')->ulid(); // Fails - * - * @return self Enables fluent chaining of assertion methods. - */ - public function ulid(): self - { - return $this->matchesRegularExpression(RegularExpressionPattern::ULID); - } } \ No newline at end of file diff --git a/src/Traits/StringAssertions.php b/src/Traits/StringAssertions.php index 7e41f73..3fc7d28 100644 --- a/src/Traits/StringAssertions.php +++ b/src/Traits/StringAssertions.php @@ -5,6 +5,7 @@ namespace K2gl\PHPUnitFluentAssertions\Traits; use K2gl\PHPUnitFluentAssertions\FluentAssertions; +use K2gl\PHPUnitFluentAssertions\Reference\RegularExpressionPattern; use PHPUnit\Framework\Assert; /** @@ -333,5 +334,23 @@ public function isValidEmail(string $message = ''): self return $this; } + /** + * Asserts that a variable is a valid ULID. + * + * This method checks if the actual value matches the ULID (Universally Unique Lexicographically Sortable Identifier) format. + * + * @see https://github.com/ulid/spec + * + * Example usage: + * fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID) + * fact('invalid-ulid')->ulid(); // Fails + * + * @return self Enables fluent chaining of assertion methods. + */ + public function ulid(): self + { + return $this->matchesRegularExpression(RegularExpressionPattern::ULID); + } + // endregion Length Methods } \ No newline at end of file From 391920de56d628e3a37130b0bc84d87b9eb22867 Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 05:42:32 +0500 Subject: [PATCH 3/9] Add every, some, none methods to ArrayAssertions with tests and README updates --- README.md | 72 ++------------ src/FluentAssertions.php | 2 - src/Traits/ArrayAssertions.php | 99 +++++++++++++++++++ src/Traits/SpecialAssertions.php | 15 --- src/Traits/StringAssertions.php | 2 +- .../Asserts/Every/EveryTest.php | 51 ++++++++++ .../Asserts/None/NoneTest.php | 51 ++++++++++ .../Asserts/Some/SomeTest.php | 51 ++++++++++ 8 files changed, 262 insertions(+), 81 deletions(-) delete mode 100644 src/Traits/SpecialAssertions.php create mode 100644 tests/FluentAssertions/Asserts/Every/EveryTest.php create mode 100644 tests/FluentAssertions/Asserts/None/NoneTest.php create mode 100644 tests/FluentAssertions/Asserts/Some/SomeTest.php diff --git a/README.md b/README.md index 2028ca5..845cb19 100644 --- a/README.md +++ b/README.md @@ -65,72 +65,16 @@ fact([])->isEmptyArray(); // Passes fact([1, 2])->isEmptyArray(); // Fails fact([1, 2])->isNotEmptyArray(); // Passes -fact([])->isNotEmptyArray(); // Fails +fact([])->isNotEmptyArray(); // Fails -``` - -### Boolean assertions -```php -fact(true)->true(); // Passes -fact(1)->true(); // Fails due to strict comparison - -fact(false)->notTrue(); // Passes -fact(true)->notTrue(); // Fails - -fact(false)->false(); // Passes -fact(0)->false(); // Fails due to strict comparison - -fact(true)->notFalse(); // Passes -fact(false)->notFalse(); // Fails -``` - - -### Comparison and equality assertions -```php -fact(42)->is(42); // Passes -fact(42)->is('42'); // Fails due to type difference - -fact(42)->equals(42); // Passes -fact(42)->equals('42'); // Passes due to loose comparison - -fact(42)->not(43); // Passes -fact(42)->not(42); // Fails -``` +fact([2, 4, 6])->every(fn($v) => $v % 2 === 0); // Passes +fact([1, 2, 3])->every(fn($v) => $v > 5); // Fails +fact([1, 2, 3])->some(fn($v) => $v > 2); // Passes +fact([1, 2, 3])->some(fn($v) => $v > 10); // Fails -### Null assertions -```php -fact(null)->null(); // Passes -fact('')->null(); // Fails - -fact(42)->notNull(); // Passes -fact(null)->notNull(); // Fails -``` - -### Numeric assertions -```php -fact(5)->isLowerThan(10); // Passes -fact(10)->isLowerThan(5); // Fails - -fact(10)->isGreaterThan(5); // Passes -fact(5)->isGreaterThan(10); // Fails - -fact(5)->isPositive(); // Passes -fact(-3)->isPositive(); // Fails - -fact(-3)->isNegative(); // Passes -fact(5)->isNegative(); // Fails - -fact(0)->isZero(); // Passes -fact(0.0)->isZero(); // Passes -fact(1)->isZero(); // Fails - -fact(5)->isBetween(1, 10); // Passes -fact(15)->isBetween(1, 10); // Fails -``` - -### Special assertions -```php +fact([1, 2, 3])->none(fn($v) => $v > 10); // Passes +fact([1, 2, 3])->none(fn($v) => $v > 2); // Fails ``` ### String assertions @@ -174,6 +118,8 @@ fact('invalid json')->isJson(); // Fails fact('user@example.com')->isValidEmail(); // Passes fact('invalid-email')->isValidEmail(); // Fails +fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID) +fact('invalid-ulid')->ulid(); // Fails ``` ### Type Checking assertions diff --git a/src/FluentAssertions.php b/src/FluentAssertions.php index 3ca73ea..23bfa3b 100644 --- a/src/FluentAssertions.php +++ b/src/FluentAssertions.php @@ -9,7 +9,6 @@ use K2gl\PHPUnitFluentAssertions\Traits\ComparisonAndEqualityAssertions; use K2gl\PHPUnitFluentAssertions\Traits\NullAssertions; use K2gl\PHPUnitFluentAssertions\Traits\NumericAssertions; -use K2gl\PHPUnitFluentAssertions\Traits\SpecialAssertions; use K2gl\PHPUnitFluentAssertions\Traits\StringAssertions; use K2gl\PHPUnitFluentAssertions\Traits\TypeCheckingAssertions; @@ -22,7 +21,6 @@ class FluentAssertions use StringAssertions; use ArrayAssertions; use TypeCheckingAssertions; - use SpecialAssertions; public function __construct( public readonly mixed $variable = null diff --git a/src/Traits/ArrayAssertions.php b/src/Traits/ArrayAssertions.php index 973cf2c..8168445 100644 --- a/src/Traits/ArrayAssertions.php +++ b/src/Traits/ArrayAssertions.php @@ -240,4 +240,103 @@ public function isNotEmptyArray(string $message = ''): self return $this; } + + /** + * Asserts that every element in the array satisfies the given callback. + * + * This method checks if all elements pass the condition defined by the callback. + * The callback receives the value and optionally the key. + * + * Example usage: + * fact([2, 4, 6])->every(fn($v) => $v % 2 === 0); // Passes + * fact([1, 2, 3])->every(fn($v) => $v > 5); // Fails + * + * @param callable $callback The function to test each element (receives value and key). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function every(callable $callback, string $message = ''): self + { + $array = $this->variable; + if (!is_array($array)) { + Assert::assertTrue(false, $message ?: 'Variable is not an array.'); + } + if (empty($array)) { + Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.'); + } + foreach ($array as $key => $value) { + if (!$callback($value, $key)) { + Assert::assertTrue(false, $message ?: 'Not all elements satisfy the condition.'); + } + } + Assert::assertTrue(true, $message); + return $this; + } + + /** + * Asserts that at least one element in the array satisfies the given callback. + * + * This method checks if any element passes the condition defined by the callback. + * The callback receives the value and optionally the key. + * + * Example usage: + * fact([1, 2, 3])->some(fn($v) => $v > 2); // Passes + * fact([1, 2, 3])->some(fn($v) => $v > 10); // Fails + * + * @param callable $callback The function to test each element (receives value and key). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function some(callable $callback, string $message = ''): self + { + $array = $this->variable; + if (!is_array($array)) { + Assert::assertTrue(false, $message ?: 'Variable is not an array.'); + } + if (empty($array)) { + Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.'); + } + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + Assert::assertTrue(true, $message); + return $this; + } + } + Assert::assertTrue(false, $message ?: 'No elements satisfy the condition.'); + } + + /** + * Asserts that no elements in the array satisfy the given callback. + * + * This method checks if none of the elements pass the condition defined by the callback. + * The callback receives the value and optionally the key. + * + * Example usage: + * fact([1, 2, 3])->none(fn($v) => $v > 10); // Passes + * fact([1, 2, 3])->none(fn($v) => $v > 2); // Fails + * + * @param callable $callback The function to test each element (receives value and key). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function none(callable $callback, string $message = ''): self + { + $array = $this->variable; + if (!is_array($array)) { + Assert::assertTrue(false, $message ?: 'Variable is not an array.'); + } + if (empty($array)) { + Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.'); + } + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + Assert::assertTrue(false, $message ?: 'At least one element satisfies the condition.'); + } + } + Assert::assertTrue(true, $message); + return $this; + } } \ No newline at end of file diff --git a/src/Traits/SpecialAssertions.php b/src/Traits/SpecialAssertions.php deleted file mode 100644 index 3fa43ae..0000000 --- a/src/Traits/SpecialAssertions.php +++ /dev/null @@ -1,15 +0,0 @@ -every($callback); + + // assert + $this->correctAssertionExecuted(); + } + + #[DataProvider('notEveryDataProvider')] + public function testNotEvery(mixed $variable, callable $callback): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($variable)->every($callback); + } + + public static function everyDataProvider(): array + { + return [ + [[2, 4, 6], fn($v) => $v % 2 === 0], + [['a' => 1, 'b' => 3], fn($v, $k) => $v > 0], + ]; + } + + public static function notEveryDataProvider(): array + { + return [ + [[1, 2, 3], fn($v) => $v > 5], + [[], fn($v) => true], // empty array + ['not array', fn($v) => true], // not array + ]; + } +} \ No newline at end of file diff --git a/tests/FluentAssertions/Asserts/None/NoneTest.php b/tests/FluentAssertions/Asserts/None/NoneTest.php new file mode 100644 index 0000000..2f73e4b --- /dev/null +++ b/tests/FluentAssertions/Asserts/None/NoneTest.php @@ -0,0 +1,51 @@ +none($callback); + + // assert + $this->correctAssertionExecuted(); + } + + #[DataProvider('notNoneDataProvider')] + public function testNotNone(mixed $variable, callable $callback): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($variable)->none($callback); + } + + public static function noneDataProvider(): array + { + return [ + [[1, 2, 3], fn($v) => $v > 10], + [['a' => 1, 'b' => 2], fn($v, $k) => $k === 'c'], + ]; + } + + public static function notNoneDataProvider(): array + { + return [ + [[1, 2, 3], fn($v) => $v > 2], + [[], fn($v) => true], // empty array + ['not array', fn($v) => true], // not array + ]; + } +} \ No newline at end of file diff --git a/tests/FluentAssertions/Asserts/Some/SomeTest.php b/tests/FluentAssertions/Asserts/Some/SomeTest.php new file mode 100644 index 0000000..fec4385 --- /dev/null +++ b/tests/FluentAssertions/Asserts/Some/SomeTest.php @@ -0,0 +1,51 @@ +some($callback); + + // assert + $this->correctAssertionExecuted(); + } + + #[DataProvider('notSomeDataProvider')] + public function testNotSome(mixed $variable, callable $callback): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($variable)->some($callback); + } + + public static function someDataProvider(): array + { + return [ + [[1, 2, 3], fn($v) => $v > 2], + [['a' => 1, 'b' => 2], fn($v, $k) => $k === 'b'], + ]; + } + + public static function notSomeDataProvider(): array + { + return [ + [[1, 2, 3], fn($v) => $v > 10], + [[], fn($v) => true], // empty array + ['not array', fn($v) => true], // not array + ]; + } +} \ No newline at end of file From 49080d4e50c8394a9494dc9c85f5d44a26f62644 Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 05:53:01 +0500 Subject: [PATCH 4/9] CS & Update README.md --- README.md | 58 ++++++++++++++++++++++++++++++++++ src/Traits/ArrayAssertions.php | 15 +++++++++ 2 files changed, 73 insertions(+) diff --git a/README.md b/README.md index 845cb19..56c68ef 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,64 @@ fact([1, 2, 3])->none(fn($v) => $v > 10); // Passes fact([1, 2, 3])->none(fn($v) => $v > 2); // Fails ``` +### Boolean assertions +```php +fact(true)->true(); // Passes +fact(1)->true(); // Fails due to strict comparison + +fact(false)->notTrue(); // Passes +fact(true)->notTrue(); // Fails + +fact(false)->false(); // Passes +fact(0)->false(); // Fails due to strict comparison + +fact(true)->notFalse(); // Passes +fact(false)->notFalse(); // Fails +``` + +### Comparison and equality assertions +```php +fact(42)->is(42); // Passes +fact(42)->is('42'); // Fails due to type difference + +fact(42)->equals(42); // Passes +fact(42)->equals('42'); // Passes due to loose comparison + +fact(42)->not(43); // Passes +fact(42)->not(42); // Fails +``` + +### Null assertions +```php +fact(null)->null(); // Passes +fact('')->null(); // Fails + +fact(42)->notNull(); // Passes +fact(null)->notNull(); // Fails +``` + +### Numeric assertions +```php +fact(5)->isLowerThan(10); // Passes +fact(10)->isLowerThan(5); // Fails + +fact(10)->isGreaterThan(5); // Passes +fact(5)->isGreaterThan(10); // Fails + +fact(5)->isPositive(); // Passes +fact(-3)->isPositive(); // Fails + +fact(-3)->isNegative(); // Passes +fact(5)->isNegative(); // Fails + +fact(0)->isZero(); // Passes +fact(0.0)->isZero(); // Passes +fact(1)->isZero(); // Fails + +fact(5)->isBetween(1, 10); // Passes +fact(15)->isBetween(1, 10); // Fails +``` + ### String assertions ```php fact('abc123')->matchesRegularExpression('/^[a-z]+\d+$/'); // Passes diff --git a/src/Traits/ArrayAssertions.php b/src/Traits/ArrayAssertions.php index 8168445..2407a1d 100644 --- a/src/Traits/ArrayAssertions.php +++ b/src/Traits/ArrayAssertions.php @@ -259,18 +259,23 @@ public function isNotEmptyArray(string $message = ''): self public function every(callable $callback, string $message = ''): self { $array = $this->variable; + if (!is_array($array)) { Assert::assertTrue(false, $message ?: 'Variable is not an array.'); } + if (empty($array)) { Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.'); } + foreach ($array as $key => $value) { if (!$callback($value, $key)) { Assert::assertTrue(false, $message ?: 'Not all elements satisfy the condition.'); } } + Assert::assertTrue(true, $message); + return $this; } @@ -292,18 +297,23 @@ public function every(callable $callback, string $message = ''): self public function some(callable $callback, string $message = ''): self { $array = $this->variable; + if (!is_array($array)) { Assert::assertTrue(false, $message ?: 'Variable is not an array.'); } + if (empty($array)) { Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.'); } + foreach ($array as $key => $value) { if ($callback($value, $key)) { Assert::assertTrue(true, $message); + return $this; } } + Assert::assertTrue(false, $message ?: 'No elements satisfy the condition.'); } @@ -325,18 +335,23 @@ public function some(callable $callback, string $message = ''): self public function none(callable $callback, string $message = ''): self { $array = $this->variable; + if (!is_array($array)) { Assert::assertTrue(false, $message ?: 'Variable is not an array.'); } + if (empty($array)) { Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.'); } + foreach ($array as $key => $value) { if ($callback($value, $key)) { Assert::assertTrue(false, $message ?: 'At least one element satisfies the condition.'); } } + Assert::assertTrue(true, $message); + return $this; } } \ No newline at end of file From 61930519664630f1b34c3d1b172d2aaaacff4f12 Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 05:55:19 +0500 Subject: [PATCH 5/9] Fix PHPDoc return type in StringAssertions for matchesRegularExpression --- src/Traits/StringAssertions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/StringAssertions.php b/src/Traits/StringAssertions.php index 9302b0e..3fc7d28 100644 --- a/src/Traits/StringAssertions.php +++ b/src/Traits/StringAssertions.php @@ -27,7 +27,7 @@ trait StringAssertions * @param string $pattern The regular expression pattern to match against. * @param string $message Optional custom error message. * - * @return StringAssertions|FluentAssertions Enables fluent chaining of assertion methods. + * @return self Enables fluent chaining of assertion methods. */ public function matchesRegularExpression(string $pattern, string $message = ''): self { From e7edf1d5c4c05be52b832aa7f8c86ed711194e2a Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 06:02:11 +0500 Subject: [PATCH 6/9] Add isResource and isCallable type checking methods with tests and README updates --- README.md | 15 ++++++ src/Traits/TypeCheckingAssertions.php | 40 ++++++++++++++ .../Asserts/IsCallable/IsCallableTest.php | 53 +++++++++++++++++++ .../Asserts/IsResource/IsResourceTest.php | 48 +++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 tests/FluentAssertions/Asserts/IsCallable/IsCallableTest.php create mode 100644 tests/FluentAssertions/Asserts/IsResource/IsResourceTest.php diff --git a/README.md b/README.md index 56c68ef..4e0ab81 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,21 @@ fact(42)->isFloat(); // Fails fact(true)->isBool(); // Passes fact(1)->isBool(); // Fails +fact([1, 2])->isArray(); // Passes +fact('not array')->isArray(); // Fails + +fact(fopen('php://memory', 'r'))->isResource(); // Passes +fact('string')->isResource(); // Fails + +fact('strlen')->isCallable(); // Passes +fact(123)->isCallable(); // Fails + +fact(3.14)->isFloat(); // Passes +fact(42)->isFloat(); // Fails + +fact(true)->isBool(); // Passes +fact(1)->isBool(); // Fails + fact([1, 2])->isArray(); // Passes fact('not array')->isArray(); // Fails ``` diff --git a/src/Traits/TypeCheckingAssertions.php b/src/Traits/TypeCheckingAssertions.php index 1a01ae5..e07c866 100644 --- a/src/Traits/TypeCheckingAssertions.php +++ b/src/Traits/TypeCheckingAssertions.php @@ -195,4 +195,44 @@ public function isArray(string $message = ''): self return $this; } + + /** + * Asserts that a variable is of type resource. + * + * This method checks if the actual value is a resource. + * + * Example usage: + * fact(fopen('file.txt', 'r'))->isResource(); // Passes + * fact('string')->isResource(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isResource(string $message = ''): self + { + Assert::assertIsResource($this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is callable. + * + * This method checks if the actual value is callable (function, method, closure). + * + * Example usage: + * fact('strlen')->isCallable(); // Passes + * fact(123)->isCallable(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isCallable(string $message = ''): self + { + Assert::assertIsCallable($this->variable, $message); + + return $this; + } } \ No newline at end of file diff --git a/tests/FluentAssertions/Asserts/IsCallable/IsCallableTest.php b/tests/FluentAssertions/Asserts/IsCallable/IsCallableTest.php new file mode 100644 index 0000000..a820513 --- /dev/null +++ b/tests/FluentAssertions/Asserts/IsCallable/IsCallableTest.php @@ -0,0 +1,53 @@ +isCallable(); + + // assert + $this->correctAssertionExecuted(); + } + + #[DataProvider('notIsCallableDataProvider')] + public function testNotIsCallable(mixed $variable): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($variable)->isCallable(); + } + + public static function isCallableDataProvider(): array + { + return [ + ['strlen'], + [fn() => true], + ]; + } + + public static function notIsCallableDataProvider(): array + { + return [ + ['string'], + [42], + [true], + [null], + [[]], + ]; + } +} \ No newline at end of file diff --git a/tests/FluentAssertions/Asserts/IsResource/IsResourceTest.php b/tests/FluentAssertions/Asserts/IsResource/IsResourceTest.php new file mode 100644 index 0000000..4724c75 --- /dev/null +++ b/tests/FluentAssertions/Asserts/IsResource/IsResourceTest.php @@ -0,0 +1,48 @@ +isResource(); + + // assert + $this->correctAssertionExecuted(); + + fclose($resource); + } + + #[DataProvider('notIsResourceDataProvider')] + public function testNotIsResource(mixed $variable): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($variable)->isResource(); + } + + public static function notIsResourceDataProvider(): array + { + return [ + ['string'], + [42], + [true], + [null], + [[]], + ]; + } +} \ No newline at end of file From 1179755a0a13f7904127079d36130b00323236d1 Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 06:12:26 +0500 Subject: [PATCH 7/9] Update AGENTS.md: specify PHPStan for lint/typecheck and add dependency management rule --- AGENTS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 2427e44..56f6e12 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,7 @@ This file outlines the requirements and best practices for adding new assertion - **Version Compliance**: Always use the most current version of AGENTS.md and do not rely on cached or outdated copies. Refresh and re-read AGENTS.md before every change to ensure compliance with the latest requirements. - **Branching and Commits**: It is forbidden to commit directly to the `main` branch. All changes must be added via pull request from a feature branch. If the current branch is `main`, MUST checkout to a new branch before changing any files. Do not push changes automatically—only push after explicit user request. - **Pull Requests**: When creating a pull request, update the PR description with a detailed summary of changes, including new methods added, files modified, and any breaking changes. Ensure the description follows the format: Summary, Changes, Testing, Validation. +- **Dependency Management**: Remove unused dependencies from composer.json. If a tool (e.g., Psalm) is no longer used, remove its require-dev entry and update scripts accordingly. - **Method Signature**: All new methods must be public, accept an optional `$message` parameter (string, default empty), and return `self` to enable fluent chaining. - **Type Safety**: Specify strict types for parameters where applicable (e.g., `int|float` for numeric comparisons). Avoid `mixed` unless necessary. - **PHPUnit Integration**: Use appropriate PHPUnit assertion methods (e.g., `Assert::assertLessThan`) without named parameters for compatibility. @@ -49,7 +50,7 @@ This file outlines the requirements and best practices for adding new assertion ## Validation Steps - **Run Tests**: Execute `./vendor/bin/phpunit tests/FluentAssertions/Asserts/MethodName/MethodNameTest.php` to verify implementation. -- **Lint and Typecheck**: Run linting and type checking commands (e.g., via composer scripts or direct tools) to ensure code quality. +- **Lint and Typecheck**: Run PHPStan static analysis via `composer run analyze` to ensure code quality. - **Integration**: Ensure the method works in the overall fluent chain without breaking existing functionality. ## Example Workflow for Adding `isGreaterThan` From aa0ec7349151d8e3e3414f20a264603135e93ccb Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 06:20:53 +0500 Subject: [PATCH 8/9] Implement suggestions: test suites in phpunit.xml, jsonEquals method, isResource/isCallable types --- README.md | 6 +-- phpunit.xml | 15 ++++++ src/Traits/StringAssertions.php | 34 +++++++++++++ .../Asserts/JsonEquals/JsonEqualsTest.php | 51 +++++++++++++++++++ 4 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php diff --git a/README.md b/README.md index 4e0ab81..6d2199d 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,9 @@ fact('invalid-email')->isValidEmail(); // Fails fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID) fact('invalid-ulid')->ulid(); // Fails + +fact('{"key": "value"}')->jsonEquals('{"key": "value"}'); // Passes +fact('{"key": "value"}')->jsonEquals('{"key": "different"}'); // Fails ``` ### Type Checking assertions @@ -220,9 +223,6 @@ fact(42)->isFloat(); // Fails fact(true)->isBool(); // Passes fact(1)->isBool(); // Fails - -fact([1, 2])->isArray(); // Passes -fact('not array')->isArray(); // Fails ``` ## Pull requests are always welcome diff --git a/phpunit.xml b/phpunit.xml index 9db7f9a..17f7cb5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -4,6 +4,21 @@ tests + + tests/FluentAssertions/Asserts + + + tests/FluentAssertions/Asserts + + + tests/FluentAssertions/Asserts + + + tests/FluentAssertions/Asserts + + + tests/FluentAssertions/Asserts + diff --git a/src/Traits/StringAssertions.php b/src/Traits/StringAssertions.php index 3fc7d28..381c2ad 100644 --- a/src/Traits/StringAssertions.php +++ b/src/Traits/StringAssertions.php @@ -334,6 +334,40 @@ public function isValidEmail(string $message = ''): self return $this; } + /** + * Asserts that two JSON strings are equal. + * + * This method decodes both JSON strings and compares the resulting data structures. + * + * Example usage: + * fact('{"key": "value"}')->jsonEquals('{"key": "value"}'); // Passes + * fact('{"key": "value"}')->jsonEquals('{"key": "different"}'); // Fails + * + * @param string $expectedJson The expected JSON string. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function jsonEquals(string $expectedJson, string $message = ''): self + { + $actual = json_decode($this->variable, true); + $actualError = json_last_error(); + + $expected = json_decode($expectedJson, true); + $expectedError = json_last_error(); + + if ($actualError !== JSON_ERROR_NONE) { + Assert::assertTrue(false, $message ?: 'Actual JSON is invalid.'); + } + if ($expectedError !== JSON_ERROR_NONE) { + Assert::assertTrue(false, $message ?: 'Expected JSON is invalid.'); + } + + Assert::assertEquals($expected, $actual, $message ?: 'JSON strings are not equal.'); + + return $this; + } + /** * Asserts that a variable is a valid ULID. * diff --git a/tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php b/tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php new file mode 100644 index 0000000..659e4d0 --- /dev/null +++ b/tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php @@ -0,0 +1,51 @@ +jsonEquals($expected); + + // assert + $this->correctAssertionExecuted(); + } + + #[DataProvider('notJsonEqualsDataProvider')] + public function testNotJsonEquals(string $actual, string $expected): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($actual)->jsonEquals($expected); + } + + public static function jsonEqualsDataProvider(): array + { + return [ + ['{"key": "value"}', '{"key": "value"}'], + ['[1, 2, 3]', '[1, 2, 3]'], + ]; + } + + public static function notJsonEqualsDataProvider(): array + { + return [ + ['{"key": "value"}', '{"key": "different"}'], + ['invalid json', '{"key": "value"}'], // invalid actual + ['{"key": "value"}', 'invalid json'], // invalid expected + ]; + } +} \ No newline at end of file From e05e2f8c7d8745c266d3b039a91fd37aefe83c4e Mon Sep 17 00:00:00 2001 From: l0gic Date: Thu, 22 Jan 2026 06:23:50 +0500 Subject: [PATCH 9/9] Remove jsonEquals method and test as per request --- README.md | 3 -- src/Traits/StringAssertions.php | 32 ------------ .../Asserts/JsonEquals/JsonEqualsTest.php | 51 ------------------- 3 files changed, 86 deletions(-) delete mode 100644 tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php diff --git a/README.md b/README.md index 6d2199d..3936d13 100644 --- a/README.md +++ b/README.md @@ -178,9 +178,6 @@ fact('invalid-email')->isValidEmail(); // Fails fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID) fact('invalid-ulid')->ulid(); // Fails - -fact('{"key": "value"}')->jsonEquals('{"key": "value"}'); // Passes -fact('{"key": "value"}')->jsonEquals('{"key": "different"}'); // Fails ``` ### Type Checking assertions diff --git a/src/Traits/StringAssertions.php b/src/Traits/StringAssertions.php index 381c2ad..00e078c 100644 --- a/src/Traits/StringAssertions.php +++ b/src/Traits/StringAssertions.php @@ -334,39 +334,7 @@ public function isValidEmail(string $message = ''): self return $this; } - /** - * Asserts that two JSON strings are equal. - * - * This method decodes both JSON strings and compares the resulting data structures. - * - * Example usage: - * fact('{"key": "value"}')->jsonEquals('{"key": "value"}'); // Passes - * fact('{"key": "value"}')->jsonEquals('{"key": "different"}'); // Fails - * - * @param string $expectedJson The expected JSON string. - * @param string $message Optional custom error message. - * - * @return self Enables fluent chaining of assertion methods. - */ - public function jsonEquals(string $expectedJson, string $message = ''): self - { - $actual = json_decode($this->variable, true); - $actualError = json_last_error(); - - $expected = json_decode($expectedJson, true); - $expectedError = json_last_error(); - - if ($actualError !== JSON_ERROR_NONE) { - Assert::assertTrue(false, $message ?: 'Actual JSON is invalid.'); - } - if ($expectedError !== JSON_ERROR_NONE) { - Assert::assertTrue(false, $message ?: 'Expected JSON is invalid.'); - } - - Assert::assertEquals($expected, $actual, $message ?: 'JSON strings are not equal.'); - return $this; - } /** * Asserts that a variable is a valid ULID. diff --git a/tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php b/tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php deleted file mode 100644 index 659e4d0..0000000 --- a/tests/FluentAssertions/Asserts/JsonEquals/JsonEqualsTest.php +++ /dev/null @@ -1,51 +0,0 @@ -jsonEquals($expected); - - // assert - $this->correctAssertionExecuted(); - } - - #[DataProvider('notJsonEqualsDataProvider')] - public function testNotJsonEquals(string $actual, string $expected): void - { - // assert - $this->incorrectAssertionExpected(); - - // act - fact($actual)->jsonEquals($expected); - } - - public static function jsonEqualsDataProvider(): array - { - return [ - ['{"key": "value"}', '{"key": "value"}'], - ['[1, 2, 3]', '[1, 2, 3]'], - ]; - } - - public static function notJsonEqualsDataProvider(): array - { - return [ - ['{"key": "value"}', '{"key": "different"}'], - ['invalid json', '{"key": "value"}'], // invalid actual - ['{"key": "value"}', 'invalid json'], // invalid expected - ]; - } -} \ No newline at end of file