Skip to content

Commit 2a5410d

Browse files
authored
12.3.1
* Remove Psalm: remove psalm/phar dependency, psalm.xml, and update scripts * Move ulid method from SpecialAssertions to StringAssertions and update README * Add every, some, none methods to ArrayAssertions with tests and README updates * CS & Update README.md * Fix PHPDoc return type in StringAssertions for matchesRegularExpression * Add isResource and isCallable type checking methods with tests and README updates * Update AGENTS.md: specify PHPStan for lint/typecheck and add dependency management rule * Implement suggestions: test suites in phpunit.xml, jsonEquals method, isResource/isCallable types * Remove jsonEquals method and test as per request
1 parent 1fd1b12 commit 2a5410d

File tree

15 files changed

+479
-77
lines changed

15 files changed

+479
-77
lines changed

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This file outlines the requirements and best practices for adding new assertion
77
- **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.
88
- **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.
99
- **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.
10+
- **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.
1011
- **Method Signature**: All new methods must be public, accept an optional `$message` parameter (string, default empty), and return `self` to enable fluent chaining.
1112
- **Type Safety**: Specify strict types for parameters where applicable (e.g., `int|float` for numeric comparisons). Avoid `mixed` unless necessary.
1213
- **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
4950
## Validation Steps
5051

5152
- **Run Tests**: Execute `./vendor/bin/phpunit tests/FluentAssertions/Asserts/MethodName/MethodNameTest.php` to verify implementation.
52-
- **Lint and Typecheck**: Run linting and type checking commands (e.g., via composer scripts or direct tools) to ensure code quality.
53+
- **Lint and Typecheck**: Run PHPStan static analysis via `composer run analyze` to ensure code quality.
5354
- **Integration**: Ensure the method works in the overall fluent chain without breaking existing functionality.
5455

5556
## Example Workflow for Adding `isGreaterThan`

README.md

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,16 @@ fact([])->isEmptyArray(); // Passes
6565
fact([1, 2])->isEmptyArray(); // Fails
6666

6767
fact([1, 2])->isNotEmptyArray(); // Passes
68-
fact([])->isNotEmptyArray(); // Fails
68+
fact([])->isNotEmptyArray(); // Fails
6969

70+
fact([2, 4, 6])->every(fn($v) => $v % 2 === 0); // Passes
71+
fact([1, 2, 3])->every(fn($v) => $v > 5); // Fails
72+
73+
fact([1, 2, 3])->some(fn($v) => $v > 2); // Passes
74+
fact([1, 2, 3])->some(fn($v) => $v > 10); // Fails
75+
76+
fact([1, 2, 3])->none(fn($v) => $v > 10); // Passes
77+
fact([1, 2, 3])->none(fn($v) => $v > 2); // Fails
7078
```
7179

7280
### Boolean assertions
@@ -84,8 +92,7 @@ fact(true)->notFalse(); // Passes
8492
fact(false)->notFalse(); // Fails
8593
```
8694

87-
88-
### Comparison and Equality assertions
95+
### Comparison and equality assertions
8996
```php
9097
fact(42)->is(42); // Passes
9198
fact(42)->is('42'); // Fails due to type difference
@@ -97,7 +104,6 @@ fact(42)->not(43); // Passes
97104
fact(42)->not(42); // Fails
98105
```
99106

100-
101107
### Null assertions
102108
```php
103109
fact(null)->null(); // Passes
@@ -114,7 +120,7 @@ fact(10)->isLowerThan(5); // Fails
114120

115121
fact(10)->isGreaterThan(5); // Passes
116122
fact(5)->isGreaterThan(10); // Fails
117-
123+
118124
fact(5)->isPositive(); // Passes
119125
fact(-3)->isPositive(); // Fails
120126

@@ -129,12 +135,6 @@ fact(5)->isBetween(1, 10); // Passes
129135
fact(15)->isBetween(1, 10); // Fails
130136
```
131137

132-
### Special assertions
133-
```php
134-
fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID)
135-
fact('invalid-ulid')->ulid(); // Fails
136-
```
137-
138138
### String assertions
139139
```php
140140
fact('abc123')->matchesRegularExpression('/^[a-z]+\d+$/'); // Passes
@@ -175,6 +175,9 @@ fact('invalid json')->isJson(); // Fails
175175

176176
fact('[email protected]')->isValidEmail(); // Passes
177177
fact('invalid-email')->isValidEmail(); // Fails
178+
179+
fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID)
180+
fact('invalid-ulid')->ulid(); // Fails
178181
```
179182

180183
### Type Checking assertions
@@ -205,6 +208,18 @@ fact(1)->isBool(); // Fails
205208

206209
fact([1, 2])->isArray(); // Passes
207210
fact('not array')->isArray(); // Fails
211+
212+
fact(fopen('php://memory', 'r'))->isResource(); // Passes
213+
fact('string')->isResource(); // Fails
214+
215+
fact('strlen')->isCallable(); // Passes
216+
fact(123)->isCallable(); // Fails
217+
218+
fact(3.14)->isFloat(); // Passes
219+
fact(42)->isFloat(); // Fails
220+
221+
fact(true)->isBool(); // Passes
222+
fact(1)->isBool(); // Fails
208223
```
209224

210225
## Pull requests are always welcome

composer.json

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,12 @@
3434
},
3535
"minimum-stability": "stable",
3636
"scripts": {
37-
"analyze": [
38-
"@phpstan",
39-
"@psalm"
40-
],
41-
"phpstan": "./vendor/bin/phpstan analyse",
42-
"psalm": "./vendor/bin/psalm.phar --config=psalm.xml"
37+
"analyze": [
38+
"@phpstan"
39+
],
40+
"phpstan": "./vendor/bin/phpstan analyse"
4341
},
44-
"require-dev": {
45-
"psalm/phar": "^6.14",
46-
"phpstan/phpstan": "^2.1"
47-
}
42+
"require-dev": {
43+
"phpstan/phpstan": "^2.1"
44+
}
4845
}

phpunit.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@
44
<testsuite name="default">
55
<directory>tests</directory>
66
</testsuite>
7+
<testsuite name="array">
8+
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
9+
</testsuite>
10+
<testsuite name="string">
11+
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
12+
</testsuite>
13+
<testsuite name="numeric">
14+
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
15+
</testsuite>
16+
<testsuite name="type">
17+
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
18+
</testsuite>
19+
<testsuite name="comparison">
20+
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
21+
</testsuite>
722
</testsuites>
823
<source restrictNotices="true" restrictWarnings="true" ignoreIndirectDeprecations="true">
924
<include>

psalm.xml

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/FluentAssertions.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use K2gl\PHPUnitFluentAssertions\Traits\ComparisonAndEqualityAssertions;
1010
use K2gl\PHPUnitFluentAssertions\Traits\NullAssertions;
1111
use K2gl\PHPUnitFluentAssertions\Traits\NumericAssertions;
12-
use K2gl\PHPUnitFluentAssertions\Traits\SpecialAssertions;
1312
use K2gl\PHPUnitFluentAssertions\Traits\StringAssertions;
1413
use K2gl\PHPUnitFluentAssertions\Traits\TypeCheckingAssertions;
1514

@@ -22,7 +21,6 @@ class FluentAssertions
2221
use StringAssertions;
2322
use ArrayAssertions;
2423
use TypeCheckingAssertions;
25-
use SpecialAssertions;
2624

2725
public function __construct(
2826
public readonly mixed $variable = null

src/Traits/ArrayAssertions.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,118 @@ public function isNotEmptyArray(string $message = ''): self
240240

241241
return $this;
242242
}
243+
244+
/**
245+
* Asserts that every element in the array satisfies the given callback.
246+
*
247+
* This method checks if all elements pass the condition defined by the callback.
248+
* The callback receives the value and optionally the key.
249+
*
250+
* Example usage:
251+
* fact([2, 4, 6])->every(fn($v) => $v % 2 === 0); // Passes
252+
* fact([1, 2, 3])->every(fn($v) => $v > 5); // Fails
253+
*
254+
* @param callable $callback The function to test each element (receives value and key).
255+
* @param string $message Optional custom error message.
256+
*
257+
* @return self Enables fluent chaining of assertion methods.
258+
*/
259+
public function every(callable $callback, string $message = ''): self
260+
{
261+
$array = $this->variable;
262+
263+
if (!is_array($array)) {
264+
Assert::assertTrue(false, $message ?: 'Variable is not an array.');
265+
}
266+
267+
if (empty($array)) {
268+
Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.');
269+
}
270+
271+
foreach ($array as $key => $value) {
272+
if (!$callback($value, $key)) {
273+
Assert::assertTrue(false, $message ?: 'Not all elements satisfy the condition.');
274+
}
275+
}
276+
277+
Assert::assertTrue(true, $message);
278+
279+
return $this;
280+
}
281+
282+
/**
283+
* Asserts that at least one element in the array satisfies the given callback.
284+
*
285+
* This method checks if any element passes the condition defined by the callback.
286+
* The callback receives the value and optionally the key.
287+
*
288+
* Example usage:
289+
* fact([1, 2, 3])->some(fn($v) => $v > 2); // Passes
290+
* fact([1, 2, 3])->some(fn($v) => $v > 10); // Fails
291+
*
292+
* @param callable $callback The function to test each element (receives value and key).
293+
* @param string $message Optional custom error message.
294+
*
295+
* @return self Enables fluent chaining of assertion methods.
296+
*/
297+
public function some(callable $callback, string $message = ''): self
298+
{
299+
$array = $this->variable;
300+
301+
if (!is_array($array)) {
302+
Assert::assertTrue(false, $message ?: 'Variable is not an array.');
303+
}
304+
305+
if (empty($array)) {
306+
Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.');
307+
}
308+
309+
foreach ($array as $key => $value) {
310+
if ($callback($value, $key)) {
311+
Assert::assertTrue(true, $message);
312+
313+
return $this;
314+
}
315+
}
316+
317+
Assert::assertTrue(false, $message ?: 'No elements satisfy the condition.');
318+
}
319+
320+
/**
321+
* Asserts that no elements in the array satisfy the given callback.
322+
*
323+
* This method checks if none of the elements pass the condition defined by the callback.
324+
* The callback receives the value and optionally the key.
325+
*
326+
* Example usage:
327+
* fact([1, 2, 3])->none(fn($v) => $v > 10); // Passes
328+
* fact([1, 2, 3])->none(fn($v) => $v > 2); // Fails
329+
*
330+
* @param callable $callback The function to test each element (receives value and key).
331+
* @param string $message Optional custom error message.
332+
*
333+
* @return self Enables fluent chaining of assertion methods.
334+
*/
335+
public function none(callable $callback, string $message = ''): self
336+
{
337+
$array = $this->variable;
338+
339+
if (!is_array($array)) {
340+
Assert::assertTrue(false, $message ?: 'Variable is not an array.');
341+
}
342+
343+
if (empty($array)) {
344+
Assert::assertTrue(false, $message ?: 'Array is empty, cannot evaluate condition on elements.');
345+
}
346+
347+
foreach ($array as $key => $value) {
348+
if ($callback($value, $key)) {
349+
Assert::assertTrue(false, $message ?: 'At least one element satisfies the condition.');
350+
}
351+
}
352+
353+
Assert::assertTrue(true, $message);
354+
355+
return $this;
356+
}
243357
}

src/Traits/SpecialAssertions.php

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/Traits/StringAssertions.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace K2gl\PHPUnitFluentAssertions\Traits;
66

77
use K2gl\PHPUnitFluentAssertions\FluentAssertions;
8+
use K2gl\PHPUnitFluentAssertions\Reference\RegularExpressionPattern;
89
use PHPUnit\Framework\Assert;
910

1011
/**
@@ -333,5 +334,25 @@ public function isValidEmail(string $message = ''): self
333334
return $this;
334335
}
335336

337+
338+
339+
/**
340+
* Asserts that a variable is a valid ULID.
341+
*
342+
* This method checks if the actual value matches the ULID (Universally Unique Lexicographically Sortable Identifier) format.
343+
*
344+
* @see https://github.com/ulid/spec
345+
*
346+
* Example usage:
347+
* fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID)
348+
* fact('invalid-ulid')->ulid(); // Fails
349+
*
350+
* @return self Enables fluent chaining of assertion methods.
351+
*/
352+
public function ulid(): self
353+
{
354+
return $this->matchesRegularExpression(RegularExpressionPattern::ULID);
355+
}
356+
336357
// endregion Length Methods
337358
}

0 commit comments

Comments
 (0)