Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`
Expand Down
37 changes: 26 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,16 @@ fact([])->isEmptyArray(); // Passes
fact([1, 2])->isEmptyArray(); // Fails

fact([1, 2])->isNotEmptyArray(); // Passes
fact([])->isNotEmptyArray(); // Fails
fact([])->isNotEmptyArray(); // 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

fact([1, 2, 3])->none(fn($v) => $v > 10); // Passes
fact([1, 2, 3])->none(fn($v) => $v > 2); // Fails
```

### Boolean assertions
Expand All @@ -84,8 +92,7 @@ fact(true)->notFalse(); // Passes
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
Expand All @@ -97,7 +104,6 @@ fact(42)->not(43); // Passes
fact(42)->not(42); // Fails
```


### Null assertions
```php
fact(null)->null(); // Passes
Expand All @@ -114,7 +120,7 @@ fact(10)->isLowerThan(5); // Fails

fact(10)->isGreaterThan(5); // Passes
fact(5)->isGreaterThan(10); // Fails

fact(5)->isPositive(); // Passes
fact(-3)->isPositive(); // Fails

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

### Special assertions
```php
fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); // Passes (if valid ULID)
fact('invalid-ulid')->ulid(); // Fails
```

### String assertions
```php
fact('abc123')->matchesRegularExpression('/^[a-z]+\d+$/'); // Passes
Expand Down Expand Up @@ -175,6 +175,9 @@ 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
Expand Down Expand Up @@ -205,6 +208,18 @@ 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
```

## Pull requests are always welcome
Expand Down
17 changes: 7 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
15 changes: 15 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@
<testsuite name="default">
<directory>tests</directory>
</testsuite>
<testsuite name="array">
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
</testsuite>
<testsuite name="string">
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
</testsuite>
<testsuite name="numeric">
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
</testsuite>
<testsuite name="type">
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
</testsuite>
<testsuite name="comparison">
<directory suffix="Test.php">tests/FluentAssertions/Asserts</directory>
</testsuite>
</testsuites>
<source restrictNotices="true" restrictWarnings="true" ignoreIndirectDeprecations="true">
<include>
Expand Down
21 changes: 0 additions & 21 deletions psalm.xml

This file was deleted.

2 changes: 0 additions & 2 deletions src/FluentAssertions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -22,7 +21,6 @@ class FluentAssertions
use StringAssertions;
use ArrayAssertions;
use TypeCheckingAssertions;
use SpecialAssertions;

public function __construct(
public readonly mixed $variable = null
Expand Down
114 changes: 114 additions & 0 deletions src/Traits/ArrayAssertions.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,118 @@ 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;
}
}
32 changes: 0 additions & 32 deletions src/Traits/SpecialAssertions.php

This file was deleted.

21 changes: 21 additions & 0 deletions src/Traits/StringAssertions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace K2gl\PHPUnitFluentAssertions\Traits;

use K2gl\PHPUnitFluentAssertions\FluentAssertions;
use K2gl\PHPUnitFluentAssertions\Reference\RegularExpressionPattern;
use PHPUnit\Framework\Assert;

/**
Expand Down Expand Up @@ -333,5 +334,25 @@ 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
}
Loading