diff --git a/AGENTS.md b/AGENTS.md index 8ee3b30..88dd9a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,18 +4,33 @@ This file outlines the requirements and best practices for adding new assertion ## General 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. +- **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. - **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. - **Fluent Design**: Ensure the method integrates seamlessly with the fluent interface. +## Code Organization + +- **Trait-Based Structure**: Methods are organized into separate traits in `src/Traits/` for better maintainability. + - `ComparisonAndEqualityAssertions`: Basic equality checks. + - `BooleanAssertions`: True/false assertions. + - `NullAssertions`: Null checks. + - `NumericAssertions`: Numeric comparisons (e.g., isPositive, isBetween). + - `StringAssertions`: String operations (e.g., startsWith, hasLength). + - `ArrayAssertions`: Array checks (e.g., contains, hasSize). + - `TypeCheckingAssertions`: Type validation (e.g., isInt, instanceOf, hasProperty). + - `SpecialAssertions`: Specialized checks (e.g., ULID). +- Traits are imported into `FluentAssertions` class using `use` statements. +- Place new methods in the appropriate trait based on functionality. + ## Documentation - **PHPDoc**: Provide comprehensive PHPDoc with: - Brief description of what the assertion does. - Detailed explanation of the method's behavior. - - Example usage in the docblock (placed before @param and @return). + - Example usage in the docblock (MUST always be placed before @param and @return sections—never violate this order). - `@param` tags for each parameter (including $message). - `@return self` tag for fluent chaining. - **README.md**: Update the usage section with an example of the new method in the fluent chain. diff --git a/README.md b/README.md index 0046f24..9a3a0cf 100644 --- a/README.md +++ b/README.md @@ -33,46 +33,45 @@ $user->setPhone( self::assertSame(expected: $phoneAfter, actual: $user->getPhone()); self::assertNotSame(expected: $phoneBefore, actual: $user->getPhone()); -// fluent assertions -fact($user->getPhone()) - ->is($phoneAfter) - ->equals($phoneAfter) - ->not($phoneBefore) - ->true() - ->notTrue() - ->false() - ->notFalse() - ->null() - ->notNull() - ->matchesRegularExpression('#^\d+$#') - ->notMatchesRegularExpression('#^\D+$#') - ->containsString('alpha') - ->notContainsString('alpha') - ->containsStringIgnoringCase('beta') - ->notContainsStringIgnoringCase('beta') - ->count(5) - ->notCount(5) - ->arrayHasKey('echo') - ->arrayNotHasKey('echo') - ->instanceOf(UserFactory::class) - ->notInstanceOf(UserFactory::class) - ->ulid() // Universally Unique Lexicographically Sortable Identifier https://github.com/ulid/spec - ->isLowerThan(100) - ->isGreaterThan(50) - ->isPositive() - ->isNegative() - ->isZero() - ->isBetween(1, 10) - ->startsWith('prefix') - ->endsWith('suffix') - ->hasLength(5) - ->contains('value') - ->doesNotContain('value') - ->hasSize(3) - ->isEmptyArray() - ->isEmptyString() - ->isInt() - ->isString() +### Comparison and Equality Methods +```php +fact(42)->is(42)->equals(42)->not(43); +``` + +### Boolean Methods +```php +fact(true)->true()->notFalse(); +``` + +### Null Methods +```php +fact(null)->null()->notNull(); +``` + +### Numeric Methods +```php +fact(5)->isLowerThan(10)->isGreaterThan(0)->isPositive()->isNegative()->isZero()->isBetween(1, 10); +``` + +### String Methods +```php +fact('hello world')->matchesRegularExpression('/\w+/')->startsWith('hello')->endsWith('world')->hasLength(11)->isEmptyString(); +``` + +### Array Methods +```php +fact([1, 2, 3])->count(3)->contains(2)->doesNotContain(4)->hasSize(3)->isEmptyArray()->isNotEmptyArray(); +``` + +### Type Checking Methods +```php +fact(42)->isInt()->isString()->instanceOf(int::class)->hasProperty('name')->hasMethod('doSomething'); +``` + +### Special Methods +```php +fact('01ARZ3NDEKTSV4RRFFQ69G5FAV')->ulid(); +``` ... ; diff --git a/composer.json b/composer.json index f78acfe..27380c5 100644 --- a/composer.json +++ b/composer.json @@ -27,10 +27,22 @@ "src/aliases.php" ] }, - "autoload-dev": { - "psr-4": { - "K2gl\\PHPUnitFluentAssertions\\Tests\\": "tests/" - } - }, - "minimum-stability": "stable" + "autoload-dev": { + "psr-4": { + "K2gl\\PHPUnitFluentAssertions\\Tests\\": "tests/" + } + }, + "minimum-stability": "stable", + "scripts": { + "analyze": [ + "@phpstan", + "@psalm" + ], + "phpstan": "./vendor/bin/phpstan analyse", + "psalm": "./vendor/bin/psalm.phar --config=psalm.xml" + }, + "require-dev": { + "psalm/phar": "^6.14", + "phpstan/phpstan": "^2.1" + } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..966c7b9 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 5 + paths: + - src + excludePaths: + - vendor \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..2edfffe --- /dev/null +++ b/psalm.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FluentAssertions.php b/src/FluentAssertions.php index 554217e..3ca73ea 100644 --- a/src/FluentAssertions.php +++ b/src/FluentAssertions.php @@ -4,11 +4,26 @@ namespace K2gl\PHPUnitFluentAssertions; -use K2gl\PHPUnitFluentAssertions\Reference\RegularExpressionPattern; -use PHPUnit\Framework\Assert; +use K2gl\PHPUnitFluentAssertions\Traits\ArrayAssertions; +use K2gl\PHPUnitFluentAssertions\Traits\BooleanAssertions; +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; class FluentAssertions { + use ComparisonAndEqualityAssertions; + use BooleanAssertions; + use NullAssertions; + use NumericAssertions; + use StringAssertions; + use ArrayAssertions; + use TypeCheckingAssertions; + use SpecialAssertions; + public function __construct( public readonly mixed $variable = null ) { @@ -24,856 +39,10 @@ public function __construct( * * @param mixed $variable The variable to assert on. * - * @return self Returns a new FluentAssertions instance. + * @return self Enables fluent chaining of assertion methods. */ public static function for(mixed $variable = null): self { return new FluentAssertions(variable: $variable); } - - /** - * Asserts that two variables have the same type and value. - * Used on objects, it asserts that two variables reference the same object. - * - * This method performs a strict comparison (===) between the actual value and the expected value. - * - * Example usage: - * fact(42)->is(42); // Passes - * fact(42)->is('42'); // Fails due to type difference - * - * @param mixed $expected The expected value to compare against. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function is(mixed $expected, string $message = ''): self - { - Assert::assertSame(expected: $expected, actual: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that two variables are equal. - * - * This method performs a loose comparison (==) between the actual value and the expected value. - * - * Example usage: - * fact(42)->equals(42); // Passes - * fact(42)->equals('42'); // Passes due to loose comparison - * - * @param mixed $expected The expected value to compare against. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function equals(mixed $expected, string $message = ''): self - { - Assert::assertEquals(expected: $expected, actual: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a numeric value is lower than another numeric value. - * - * This method checks if the actual value is strictly less than the expected value. - * Both values must be of type int or float. - * - * @param int|float $expected The value to compare against. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(5)->isLowerThan(10); // Passes - * fact(10)->isLowerThan(5); // Fails - */ - public function isLowerThan(int|float $expected, string $message = ''): self - { - Assert::assertLessThan($expected, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a numeric value is greater than another numeric value. - * - * This method checks if the actual value is strictly greater than the expected value. - * Both values must be of type int or float. - * - * @param int|float $expected The value to compare against. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(10)->isGreaterThan(5); // Passes - * fact(5)->isGreaterThan(10); // Fails - */ - public function isGreaterThan(int|float $expected, string $message = ''): self - { - Assert::assertGreaterThan($expected, $this->variable, $message); - - return $this; - } - - /** - * Asserts that an array is empty. - * - * This method checks if the actual value is an empty array. - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact([])->isEmptyArray(); // Passes - * fact([1, 2])->isEmptyArray(); // Fails - */ - public function isEmptyArray(string $message = ''): self - { - Assert::assertCount(0, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a string is empty. - * - * This method checks if the actual value is an empty string. - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact('')->isEmptyString(); // Passes - * fact('hello')->isEmptyString(); // Fails - */ - public function isEmptyString(string $message = ''): self - { - Assert::assertEmpty($this->variable, $message); - - return $this; - } - - /** - * Asserts that two variables do not have the same type and value. - * Used on objects, it asserts that two variables do not reference the same object. - * - * This method performs a strict comparison (!==) to ensure the actual value is not the same as the expected value. - * - * Example usage: - * fact(42)->not(43); // Passes - * fact(42)->not(42); // Fails - * - * @param mixed $expected The value that the actual value should not be. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function not(mixed $expected, string $message = ''): self - { - Assert::assertNotSame(expected: $expected, actual: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is true. - * Uses strict comparison (variable === true). - * - * This method checks if the actual value is exactly the boolean true. - * - * Example usage: - * fact(true)->true(); // Passes - * fact(1)->true(); // Fails due to strict comparison - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function true(string $message = ''): self - { - Assert::assertTrue(condition: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is not true. - * Uses strict comparison (variable !== true). - * - * This method checks if the actual value is not exactly the boolean true. - * - * Example usage: - * fact(false)->notTrue(); // Passes - * fact(true)->notTrue(); // Fails - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notTrue(string $message = ''): self - { - Assert::assertNotTrue(condition: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is false. - * Uses strict comparison (variable === false). - * - * This method checks if the actual value is exactly the boolean false. - * - * Example usage: - * fact(false)->false(); // Passes - * fact(0)->false(); // Fails due to strict comparison - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function false(string $message = ''): self - { - Assert::assertFalse(condition: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is not false. - * Uses strict comparison (variable !== false). - * - * This method checks if the actual value is not exactly the boolean false. - * - * Example usage: - * fact(true)->notFalse(); // Passes - * fact(false)->notFalse(); // Fails - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notFalse(string $message = ''): self - { - Assert::assertNotFalse(condition: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is null. - * - * This method checks if the actual value is exactly null. - * - * Example usage: - * fact(null)->null(); // Passes - * fact('')->null(); // Fails - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function null(string $message = ''): self - { - Assert::assertNull(actual: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is not null. - * - * This method checks if the actual value is not null. - * - * Example usage: - * fact(42)->notNull(); // Passes - * fact(null)->notNull(); // Fails - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notNull(string $message = ''): self - { - Assert::assertNotNull(actual: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable matches a given regular expression. - * - * This method checks if the string representation of the actual value matches the provided regex pattern. - * - * Example usage: - * fact('abc123')->matchesRegularExpression('/^[a-z]+\d+$/'); // Passes - * fact('123abc')->matchesRegularExpression('/^[a-z]+\d+$/'); // Fails - * - * @param string $pattern The regular expression pattern to match against. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function matchesRegularExpression(string $pattern, string $message = ''): self - { - Assert::assertMatchesRegularExpression(pattern: $pattern, string: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable does not match a given regular expression. - * - * This method checks if the string representation of the actual value does not match the provided regex pattern. - * - * Example usage: - * fact('123abc')->notMatchesRegularExpression('/^[a-z]+\d+$/'); // Passes - * fact('abc123')->notMatchesRegularExpression('/^[a-z]+\d+$/'); // Fails - * - * @param string $pattern The regular expression pattern that should not match. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notMatchesRegularExpression(string $pattern, string $message = ''): self - { - Assert::assertDoesNotMatchRegularExpression(pattern: $pattern, string: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable contains a string. - * - * This method checks if the string representation of the actual value contains the specified substring. - * - * Example usage: - * fact('hello world')->containsString('world'); // Passes - * fact('hello world')->containsString('foo'); // Fails - * - * @param string $string The substring to search for. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function containsString(string $string, string $message = ''): self - { - Assert::assertStringContainsString(needle: $string, haystack: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable does not contain a string. - * - * This method checks if the string representation of the actual value does not contain the specified substring. - * - * Example usage: - * fact('hello world')->notContainsString('foo'); // Passes - * fact('hello world')->notContainsString('world'); // Fails - * - * @param string $string The substring that should not be present. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notContainsString(string $string, string $message = ''): self - { - Assert::assertStringNotContainsString(needle: $string, haystack: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable contains a string, ignoring case. - * - * This method checks if the string representation of the actual value contains the specified substring, case-insensitively. - * - * Example usage: - * fact('Hello World')->containsStringIgnoringCase('world'); // Passes - * fact('Hello World')->containsStringIgnoringCase('foo'); // Fails - * - * @param string $string The substring to search for (case-insensitive). - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function containsStringIgnoringCase(string $string, string $message = ''): self - { - Assert::assertStringContainsStringIgnoringCase(needle: $string, haystack: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable does not contain a string, ignoring case. - * - * This method checks if the string representation of the actual value does not contain the specified substring, case-insensitively. - * - * Example usage: - * fact('Hello World')->notContainsStringIgnoringCase('foo'); // Passes - * fact('Hello World')->notContainsStringIgnoringCase('world'); // Fails - * - * @param string $string The substring that should not be present (case-insensitive). - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notContainsStringIgnoringCase(string $string, string $message = ''): self - { - Assert::assertStringNotContainsStringIgnoringCase( - needle: $string, - haystack: $this->variable, - message: $message - ); - - return $this; - } - - /** - * Asserts the number of elements of an array, Countable or Traversable. - * - * This method checks if the actual value has the expected number of elements. - * - * Example usage: - * fact([1, 2, 3])->count(3); // Passes - * fact([1, 2])->count(3); // Fails - * - * @param int $expectedCount The expected number of elements. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function count(int $expectedCount, string $message = ''): self - { - Assert::assertCount(expectedCount: $expectedCount, haystack: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that the number of elements is not equal to the expected count. - * - * This method checks if the actual value does not have the specified number of elements. - * - * Example usage: - * fact([1, 2])->notCount(3); // Passes - * fact([1, 2, 3])->notCount(3); // Fails - * - * @param int $elementsCount The number of elements that should not match. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notCount(int $elementsCount, string $message = ''): self - { - Assert::assertNotCount(expectedCount: $elementsCount, haystack: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that the array contains another associative array. - * - * This method checks if the actual array contains all the key-value pairs from the provided array. - * - * Example usage: - * fact(['a' => ['b' => 'c']])->arrayContainsAssociativeArray(['a' => ['b' => 'c']]); // Passes - * fact(['a' => ['b' => 'd']])->arrayContainsAssociativeArray(['a' => ['b' => 'c']]); // Fails - * - * @param array $values The associative array that should be contained within the actual array. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function arrayContainsAssociativeArray(array $values): self - { - Assert::assertTrue( - condition: $this->arrayContainsAssociativeArrayRecursive($this->variable, $values), - message: sprintf( - "Array does not contain associative array. \n\nArray: '%s' \n\nExpected values: '%s'", - var_export(value: $this->variable, return: true), - var_export(value: $values, return: true), - ) - ); - - return $this; - } - - protected function arrayContainsAssociativeArrayRecursive(array $data, array $values): bool - { - foreach ($values as $key => $value) { - if (is_array($value)) { - if (!$this->arrayContainsAssociativeArrayRecursive($data[$key], $value)) { - return false; - } - } elseif ($data[$key] !== $value) { - return false; - } - } - - return true; - } - - /** - * Asserts that the variable has a specific key. - * - * This method checks if the actual array has the specified key. - * - * Example usage: - * fact(['a' => 1])->arrayHasKey('a'); // Passes - * fact(['a' => 1])->arrayHasKey('b'); // Fails - * - * @param int|string $key The key to check for existence. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function arrayHasKey(int|string $key, string $message = ''): self - { - Assert::assertArrayHasKey(key: $key, array: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that the variable does not have a specific key. - * - * This method checks if the actual array does not have the specified key. - * - * Example usage: - * fact(['a' => 1])->arrayNotHasKey('b'); // Passes - * fact(['a' => 1])->arrayNotHasKey('a'); // Fails - * - * @param int|string $key The key that should not exist. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function arrayNotHasKey(int|string $key, string $message = ''): self - { - Assert::assertArrayNotHasKey(key: $key, array: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is an instance of a given type. - * - * This method checks if the actual value is an instance of the specified class or implements the interface. - * - * Example usage: - * fact(new stdClass())->instanceOf(stdClass::class); // Passes - * fact(new stdClass())->instanceOf(Exception::class); // Fails - * - * @param mixed $expected The class or interface name. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function instanceOf(mixed $expected, string $message = ''): self - { - Assert::assertInstanceOf(expected: $expected, actual: $this->variable, message: $message); - - return $this; - } - - /** - * Asserts that a variable is not an instance of a given type. - * - * This method checks if the actual value is not an instance of the specified class and does not implement the interface. - * - * Example usage: - * fact(new stdClass())->notInstanceOf(Exception::class); // Passes - * fact(new stdClass())->notInstanceOf(stdClass::class); // Fails - * - * @param mixed $expected The class or interface name that should not match. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - */ - public function notInstanceOf(mixed $expected, string $message = ''): self - { - Assert::assertNotInstanceOf(expected: $expected, actual: $this->variable, message: $message); - - 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 Returns the FluentAssertions instance for method chaining. - */ - public function ulid(): self - { - return $this->matchesRegularExpression(pattern: RegularExpressionPattern::ULID); - } - - /** - * Asserts that a numeric value is positive (greater than 0). - * - * This method checks if the actual value is greater than zero. - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(5)->isPositive(); // Passes - * fact(-3)->isPositive(); // Fails - */ - public function isPositive(string $message = ''): self - { - Assert::assertGreaterThan(0, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a numeric value is negative (less than 0). - * - * This method checks if the actual value is less than zero. - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(-3)->isNegative(); // Passes - * fact(5)->isNegative(); // Fails - */ - public function isNegative(string $message = ''): self - { - Assert::assertLessThan(0, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a numeric value is zero. - * - * This method checks if the actual value equals zero (supports int and float). - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(0)->isZero(); // Passes - * fact(0.0)->isZero(); // Passes - * fact(1)->isZero(); // Fails - */ - public function isZero(string $message = ''): self - { - Assert::assertEquals(0, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a numeric value is between two values (inclusive). - * - * This method checks if min <= value <= max. - * - * @param int|float $min The minimum value. - * @param int|float $max The maximum value. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(5)->isBetween(1, 10); // Passes - * fact(15)->isBetween(1, 10); // Fails - */ - public function isBetween(int|float $min, int|float $max, string $message = ''): self - { - Assert::assertTrue( - $this->variable >= $min && $this->variable <= $max, - $message ?: sprintf( - 'Failed asserting that %s is between %s and %s.', - $this->variable, - $min, - $max - ) - ); - - return $this; - } - - /** - * Asserts that a string starts with a given prefix. - * - * This method checks if the actual string starts with the specified prefix. - * - * @param string $prefix The prefix to check for. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact('hello world')->startsWith('hello'); // Passes - * fact('world hello')->startsWith('hello'); // Fails - */ - public function startsWith(string $prefix, string $message = ''): self - { - Assert::assertStringStartsWith($prefix, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a string ends with a given suffix. - * - * This method checks if the actual string ends with the specified suffix. - * - * @param string $suffix The suffix to check for. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact('file.txt')->endsWith('.txt'); // Passes - * fact('txt.file')->endsWith('.txt'); // Fails - */ - public function endsWith(string $suffix, string $message = ''): self - { - Assert::assertStringEndsWith($suffix, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a string has a specific length. - * - * This method checks if the length of the actual string equals the expected length. - * - * @param int $length The expected length. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact('abc')->hasLength(3); // Passes - * fact('abcd')->hasLength(3); // Fails - */ - public function hasLength(int $length, string $message = ''): self - { - Assert::assertEquals($length, strlen($this->variable), $message); - - return $this; - } - - /** - * Asserts that an array contains a specific value. - * - * This method checks if the actual array contains the specified value. - * - * @param mixed $value The value to check for. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact([1, 2, 3])->contains(2); // Passes - * fact([1, 2])->contains(3); // Fails - */ - public function contains(mixed $value, string $message = ''): self - { - Assert::assertContains($value, $this->variable, $message); - - return $this; - } - - /** - * Asserts that an array does not contain a specific value. - * - * This method checks if the actual array does not contain the specified value. - * - * @param mixed $value The value that should not be present. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact([1, 2])->doesNotContain(3); // Passes - * fact([1, 2, 3])->doesNotContain(3); // Fails - */ - public function doesNotContain(mixed $value, string $message = ''): self - { - Assert::assertNotContains($value, $this->variable, $message); - - return $this; - } - - /** - * Asserts that an array has a specific size. - * - * This method checks if the number of elements in the actual array equals the expected size. - * - * @param int $size The expected size. - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact([1, 2])->hasSize(2); // Passes - * fact([1, 2, 3])->hasSize(2); // Fails - */ - public function hasSize(int $size, string $message = ''): self - { - Assert::assertCount($size, $this->variable, $message); - - return $this; - } - - /** - * Asserts that a variable is of type int. - * - * This method checks if the actual value is an integer. - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact(42)->isInt(); // Passes - * fact('42')->isInt(); // Fails - */ - public function isInt(string $message = ''): self - { - Assert::assertIsInt($this->variable, $message); - - return $this; - } - - /** - * Asserts that a variable is of type string. - * - * This method checks if the actual value is a string. - * - * @param string $message Optional custom error message. - * - * @return self Returns the FluentAssertions instance for method chaining. - * - * Example usage: - * fact('text')->isString(); // Passes - * fact(42)->isString(); // Fails - */ - public function isString(string $message = ''): self - { - Assert::assertIsString($this->variable, $message); - - return $this; - } -} +} \ No newline at end of file diff --git a/src/Traits/ArrayAssertions.php b/src/Traits/ArrayAssertions.php new file mode 100644 index 0000000..973cf2c --- /dev/null +++ b/src/Traits/ArrayAssertions.php @@ -0,0 +1,243 @@ +count(3); // Passes + * fact([1, 2])->count(3); // Fails + * + * @param int $expectedCount The expected number of elements. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function count(int $expectedCount, string $message = ''): self + { + Assert::assertCount($expectedCount, $this->variable, $message); + + return $this; + } + + /** + * Asserts that the number of elements is not equal to the expected count. + * + * This method checks if the actual value does not have the specified number of elements. + * + * Example usage: + * fact([1, 2])->notCount(3); // Passes + * fact([1, 2, 3])->notCount(3); // Fails + * + * @param int $elementsCount The number of elements that should not match. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notCount(int $elementsCount, string $message = ''): self + { + Assert::assertNotCount($elementsCount, $this->variable, $message); + + return $this; + } + + /** + * Asserts that the array contains another associative array. + * + * This method checks if the actual array contains all the key-value pairs from the provided array. + * + * Example usage: + * fact(['a' => ['b' => 'c']])->arrayContainsAssociativeArray(['a' => ['b' => 'c']]); // Passes + * fact(['a' => ['b' => 'd']])->arrayContainsAssociativeArray(['a' => ['b' => 'c']]); // Fails + * + * @param array $values The associative array that should be contained within the actual array. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function arrayContainsAssociativeArray(array $values): self + { + Assert::assertTrue( + $this->arrayContainsAssociativeArrayRecursive($this->variable, $values), + sprintf( + "Array does not contain associative array. \n\nArray: '%s' \n\nExpected values: '%s'", + var_export($this->variable, true), + var_export($values, true), + ) + ); + + return $this; + } + + protected function arrayContainsAssociativeArrayRecursive(array $data, array $values): bool + { + foreach ($values as $key => $value) { + if (is_array($value)) { + if (!$this->arrayContainsAssociativeArrayRecursive($data[$key], $value)) { + return false; + } + } elseif ($data[$key] !== $value) { + return false; + } + } + + return true; + } + + /** + * Asserts that the variable has a specific key. + * + * This method checks if the actual array has the specified key. + * + * Example usage: + * fact(['a' => 1])->arrayHasKey('a'); // Passes + * fact(['a' => 1])->arrayHasKey('b'); // Fails + * + * @param int|string $key The key to check for existence. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function arrayHasKey(int|string $key, string $message = ''): self + { + Assert::assertArrayHasKey($key, $this->variable, $message); + + return $this; + } + + /** + * Asserts that the variable does not have a specific key. + * + * This method checks if the actual array does not have the specified key. + * + * Example usage: + * fact(['a' => 1])->arrayNotHasKey('b'); // Passes + * fact(['a' => 1])->arrayNotHasKey('a'); // Fails + * + * @param int|string $key The key that should not exist. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function arrayNotHasKey(int|string $key, string $message = ''): self + { + Assert::assertArrayNotHasKey($key, $this->variable, $message); + + return $this; + } + + /** + * Asserts that an array contains a specific value. + * + * This method checks if the actual array contains the specified value. + * + * Example usage: + * fact([1, 2, 3])->contains(2); // Passes + * fact([1, 2])->contains(3); // Fails + * + * @param mixed $value The value to check for. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function contains(mixed $value, string $message = ''): self + { + Assert::assertContains($value, $this->variable, $message); + + return $this; + } + + /** + * Asserts that an array does not contain a specific value. + * + * This method checks if the actual array does not contain the specified value. + * + * Example usage: + * fact([1, 2])->doesNotContain(3); // Passes + * fact([1, 2, 3])->doesNotContain(3); // Fails + * + * @param mixed $value The value that should not be present. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function doesNotContain(mixed $value, string $message = ''): self + { + Assert::assertNotContains($value, $this->variable, $message); + + return $this; + } + + /** + * Asserts that an array has a specific size. + * + * This method checks if the number of elements in the actual array equals the expected size. + * + * Example usage: + * fact([1, 2])->hasSize(2); // Passes + * fact([1, 2, 3])->hasSize(2); // Fails + * + * @param int $size The expected size. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function hasSize(int $size, string $message = ''): self + { + Assert::assertCount($size, $this->variable, $message); + + return $this; + } + + /** + * Asserts that an array is empty. + * + * This method checks if the actual value is an empty array. + * + * Example usage: + * fact([])->isEmptyArray(); // Passes + * fact([1, 2])->isEmptyArray(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isEmptyArray(string $message = ''): self + { + Assert::assertCount(0, $this->variable, $message); + + return $this; + } + + /** + * Asserts that an array is not empty. + * + * This method checks if the actual value is not an empty array. + * + * Example usage: + * fact([1, 2])->isNotEmptyArray(); // Passes + * fact([])->isNotEmptyArray(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isNotEmptyArray(string $message = ''): self + { + Assert::assertNotCount(0, $this->variable, $message); + + return $this; + } +} \ No newline at end of file diff --git a/src/Traits/BooleanAssertions.php b/src/Traits/BooleanAssertions.php new file mode 100644 index 0000000..0223a95 --- /dev/null +++ b/src/Traits/BooleanAssertions.php @@ -0,0 +1,98 @@ +true(); // Passes + * fact(1)->true(); // Fails due to strict comparison + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function true(string $message = ''): self + { + Assert::assertTrue($this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is not true. + * Uses strict comparison (variable !== true). + * + * This method checks if the actual value is not exactly the boolean true. + * + * Example usage: + * fact(false)->notTrue(); // Passes + * fact(true)->notTrue(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notTrue(string $message = ''): self + { + Assert::assertNotTrue($this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is false. + * Uses strict comparison (variable === false). + * + * This method checks if the actual value is exactly the boolean false. + * + * Example usage: + * fact(false)->false(); // Passes + * fact(0)->false(); // Fails due to strict comparison + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function false(string $message = ''): self + { + Assert::assertFalse($this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is not false. + * Uses strict comparison (variable !== false). + * + * This method checks if the actual value is not exactly the boolean false. + * + * Example usage: + * fact(true)->notFalse(); // Passes + * fact(false)->notFalse(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notFalse(string $message = ''): self + { + Assert::assertNotFalse($this->variable, $message); + + return $this; + } +} \ No newline at end of file diff --git a/src/Traits/ComparisonAndEqualityAssertions.php b/src/Traits/ComparisonAndEqualityAssertions.php new file mode 100644 index 0000000..46cb61b --- /dev/null +++ b/src/Traits/ComparisonAndEqualityAssertions.php @@ -0,0 +1,79 @@ +is(42); // Passes + * fact(42)->is('42'); // Fails due to type difference + * + * @param mixed $expected The expected value to compare against. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function is(mixed $expected, string $message = ''): self + { + Assert::assertSame($expected, $this->variable, $message); + + return $this; + } + + /** + * Asserts that two variables are equal. + * + * This method performs a loose comparison (==) between the actual value and the expected value. + * + * Example usage: + * fact(42)->equals(42); // Passes + * fact(42)->equals('42'); // Passes due to loose comparison + * + * @param mixed $expected The expected value to compare against. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function equals(mixed $expected, string $message = ''): self + { + Assert::assertEquals($expected, $this->variable, $message); + + return $this; + } + + /** + * Asserts that two variables do not have the same type and value. + * Used on objects, it asserts that two variables do not reference the same object. + * + * This method performs a strict comparison (!==) to ensure the actual value is not the same as the expected value. + * + * Example usage: + * fact(42)->not(43); // Passes + * fact(42)->not(42); // Fails + * + * @param mixed $expected The value that the actual value should not be. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function not(mixed $expected, string $message = ''): self + { + Assert::assertNotSame($expected, $this->variable, $message); + + return $this; + } +} \ No newline at end of file diff --git a/src/Traits/NullAssertions.php b/src/Traits/NullAssertions.php new file mode 100644 index 0000000..53fd76c --- /dev/null +++ b/src/Traits/NullAssertions.php @@ -0,0 +1,54 @@ +null(); // Passes + * fact('')->null(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function null(string $message = ''): self + { + Assert::assertNull($this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is not null. + * + * This method checks if the actual value is not null. + * + * Example usage: + * fact(42)->notNull(); // Passes + * fact(null)->notNull(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notNull(string $message = ''): self + { + Assert::assertNotNull($this->variable, $message); + + return $this; + } +} \ No newline at end of file diff --git a/src/Traits/NumericAssertions.php b/src/Traits/NumericAssertions.php new file mode 100644 index 0000000..cb60a76 --- /dev/null +++ b/src/Traits/NumericAssertions.php @@ -0,0 +1,161 @@ +isLowerThan(10); // Passes + * fact(10)->isLowerThan(5); // Fails + * + * @param int|float $expected The value to compare against. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isLowerThan(int|float $expected, string $message = ''): self + { + Assert::assertLessThan($expected, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a numeric value is greater than another numeric value. + * + * This method checks if the actual value is strictly greater than the expected value. + * Both values must be of type int or float. + * + * Example usage: + * fact(10)->isGreaterThan(5); // Passes + * fact(5)->isGreaterThan(10); // Fails + * + * @param int|float $expected The value to compare against. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isGreaterThan(int|float $expected, string $message = ''): self + { + Assert::assertGreaterThan($expected, $this->variable, $message); + + return $this; + } + + // endregion Comparison Methods + + // region Sign Checks + + /** + * Asserts that a numeric value is positive (greater than 0). + * + * This method checks if the actual value is greater than zero. + * + * Example usage: + * fact(5)->isPositive(); // Passes + * fact(-3)->isPositive(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isPositive(string $message = ''): self + { + Assert::assertGreaterThan(0, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a numeric value is negative (less than 0). + * + * This method checks if the actual value is less than zero. + * + * Example usage: + * fact(-3)->isNegative(); // Passes + * fact(5)->isNegative(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isNegative(string $message = ''): self + { + Assert::assertLessThan(0, $this->variable, $message); + + return $this; + } + + // endregion Sign Checks + + // region Value Checks + + /** + * Asserts that a numeric value is zero. + * + * This method checks if the actual value equals zero (supports int and float). + * + * Example usage: + * fact(0)->isZero(); // Passes + * fact(0.0)->isZero(); // Passes + * fact(1)->isZero(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isZero(string $message = ''): self + { + Assert::assertEquals(0, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a numeric value is between two values (inclusive). + * + * This method checks if min <= value <= max. + * + * Example usage: + * fact(5)->isBetween(1, 10); // Passes + * fact(15)->isBetween(1, 10); // Fails + * + * @param int|float $min The minimum value. + * @param int|float $max The maximum value. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isBetween(int|float $min, int|float $max, string $message = ''): self + { + Assert::assertTrue( + $this->variable >= $min && $this->variable <= $max, + $message ?: sprintf( + 'Failed asserting that %s is between %s and %s.', + $this->variable, + $min, + $max + ) + ); + + return $this; + } + + // endregion Value Checks +} \ No newline at end of file diff --git a/src/Traits/SpecialAssertions.php b/src/Traits/SpecialAssertions.php new file mode 100644 index 0000000..dc33542 --- /dev/null +++ b/src/Traits/SpecialAssertions.php @@ -0,0 +1,32 @@ +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 new file mode 100644 index 0000000..c229fc0 --- /dev/null +++ b/src/Traits/StringAssertions.php @@ -0,0 +1,277 @@ +matchesRegularExpression('/^[a-z]+\d+$/'); // Passes + * fact('123abc')->matchesRegularExpression('/^[a-z]+\d+$/'); // Fails + * + * @param non-empty-string $pattern The regular expression pattern to match against. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function matchesRegularExpression(string $pattern, string $message = ''): self + { + if (strlen($pattern) === 0) { + Assert::fail('Pattern cannot be an empty string.'); + } + + Assert::assertMatchesRegularExpression($pattern, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable does not match a given regular expression. + * + * This method checks if the string representation of the actual value does not match the provided regex pattern. + * + * Example usage: + * fact('123abc')->notMatchesRegularExpression('/^[a-z]+\d+$/'); // Passes + * fact('abc123')->notMatchesRegularExpression('/^[a-z]+\d+$/'); // Fails + * + * @param non-empty-string $pattern The regular expression pattern that should not match. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notMatchesRegularExpression(string $pattern, string $message = ''): self + { + if (strlen($pattern) === 0) { + Assert::fail('Pattern cannot be an empty string.'); + } + + Assert::assertDoesNotMatchRegularExpression($pattern, $this->variable, $message); + + return $this; + } + + // endregion Regex Methods + + // region Contains Methods + + /** + * Asserts that a variable contains a string. + * + * This method checks if the string representation of the actual value contains the specified substring. + * + * Example usage: + * fact('hello world')->containsString('world'); // Passes + * fact('hello world')->containsString('foo'); // Fails + * + * @param non-empty-string $string The substring to search for. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function containsString(string $string, string $message = ''): self + { + if (strlen($string) === 0) { + Assert::fail('String cannot be an empty.'); + } + + Assert::assertStringContainsString($string, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable does not contain a string. + * + * This method checks if the string representation of the actual value does not contain the specified substring. + * + * Example usage: + * fact('hello world')->notContainsString('foo'); // Passes + * fact('hello world')->notContainsString('world'); // Fails + * + * @param non-empty-string $string The substring that should not be present. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notContainsString(string $string, string $message = ''): self + { + if (strlen($string) === 0) { + Assert::fail('String cannot be an empty.'); + } + + Assert::assertStringNotContainsString($string, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable contains a string, ignoring case. + * + * This method checks if the string representation of the actual value contains the specified substring, case-insensitively. + * + * Example usage: + * fact('Hello World')->containsStringIgnoringCase('world'); // Passes + * fact('Hello World')->containsStringIgnoringCase('foo'); // Fails + * + * @param non-empty-string $string The substring to search for (case-insensitive). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function containsStringIgnoringCase(string $string, string $message = ''): self + { + if (strlen($string) === 0) { + Assert::fail('String cannot be an empty.'); + } + + Assert::assertStringContainsStringIgnoringCase($string, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable does not contain a string, ignoring case. + * + * This method checks if the string representation of the actual value does not contain the specified substring, case-insensitively. + * + * Example usage: + * fact('Hello World')->notContainsStringIgnoringCase('foo'); // Passes + * fact('Hello World')->notContainsStringIgnoringCase('world'); // Fails + * + * @param non-empty-string $string The substring that should not be present (case-insensitive). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notContainsStringIgnoringCase(string $string, string $message = ''): self + { + if (strlen($string) === 0) { + Assert::fail('String cannot be an empty.'); + } + + Assert::assertStringNotContainsStringIgnoringCase( + $string, + $this->variable, + $message + ); + + return $this; + } + + // endregion Contains Methods + + // region Prefix/Suffix Methods + + /** + * Asserts that a string starts with a given prefix. + * + * This method checks if the actual string starts with the specified prefix. + * + * Example usage: + * fact('hello world')->startsWith('hello'); // Passes + * fact('world hello')->startsWith('hello'); // Fails + * + * @param non-empty-string $prefix The prefix to check for (must not be empty). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function startsWith(string $prefix, string $message = ''): self + { + if (strlen($prefix) === 0) { + Assert::fail('Prefix cannot be an empty string.'); + } + + /** @var non-empty-string $prefix */ + Assert::assertStringStartsWith($prefix, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a string ends with a given suffix. + * + * This method checks if the actual string ends with the specified suffix. + * + * Example usage: + * fact('file.txt')->endsWith('.txt'); // Passes + * fact('txt.file')->endsWith('.txt'); // Fails + * + * @param non-empty-string $suffix The suffix to check for (must not be empty). + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function endsWith(string $suffix, string $message = ''): self + { + if (strlen($suffix) === 0) { + Assert::fail('Suffix cannot be an empty string.'); + } + + /** @var non-empty-string $suffix */ + Assert::assertStringEndsWith($suffix, $this->variable, $message); + + return $this; + } + + // endregion Prefix/Suffix Methods + + // region Length Methods + + /** + * Asserts that a string has a specific length. + * + * This method checks if the length of the actual string equals the expected length. + * + * Example usage: + * fact('abc')->hasLength(3); // Passes + * fact('abcd')->hasLength(3); // Fails + * + * @param int $length The expected length. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function hasLength(int $length, string $message = ''): self + { + Assert::assertEquals($length, strlen($this->variable), $message); + + return $this; + } + + /** + * Asserts that a string is empty. + * + * This method checks if the actual value is an empty string. + * + * Example usage: + * fact('')->isEmptyString(); // Passes + * fact('hello')->isEmptyString(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isEmptyString(string $message = ''): self + { + Assert::assertEmpty($this->variable, $message); + + return $this; + } + + // endregion Length Methods +} \ No newline at end of file diff --git a/src/Traits/TypeCheckingAssertions.php b/src/Traits/TypeCheckingAssertions.php new file mode 100644 index 0000000..4ab118f --- /dev/null +++ b/src/Traits/TypeCheckingAssertions.php @@ -0,0 +1,138 @@ +instanceOf(stdClass::class); // Passes + * fact(new stdClass())->instanceOf(Exception::class); // Fails + * + * @param mixed $expected The class or interface name. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function instanceOf(mixed $expected, string $message = ''): self + { + Assert::assertInstanceOf($expected, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is not an instance of a given type. + * + * This method checks if the actual value is not an instance of the specified class and does not implement the interface. + * + * Example usage: + * fact(new stdClass())->notInstanceOf(Exception::class); // Passes + * fact(new stdClass())->notInstanceOf(stdClass::class); // Fails + * + * @param mixed $expected The class or interface name that should not match. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function notInstanceOf(mixed $expected, string $message = ''): self + { + Assert::assertNotInstanceOf($expected, $this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is of type int. + * + * This method checks if the actual value is an integer. + * + * Example usage: + * fact(42)->isInt(); // Passes + * fact('42')->isInt(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isInt(string $message = ''): self + { + Assert::assertIsInt($this->variable, $message); + + return $this; + } + + /** + * Asserts that a variable is of type string. + * + * This method checks if the actual value is a string. + * + * Example usage: + * fact('text')->isString(); // Passes + * fact(42)->isString(); // Fails + * + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function isString(string $message = ''): self + { + Assert::assertIsString($this->variable, $message); + + return $this; + } + + /** + * Asserts that an object has a specific property. + * + * This method checks if the actual object has the specified property. + * + * Example usage: + * fact((object)['name' => 'John'])->hasProperty('name'); // Passes + * fact((object)['name' => 'John'])->hasProperty('age'); // Fails + * + * @param string $property The property name. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function hasProperty(string $property, string $message = ''): self + { + Assert::assertObjectHasProperty($property, $this->variable, $message); + + return $this; + } + + /** + * Asserts that an object has a specific method. + * + * This method checks if the actual object has the specified method. + * + * Example usage: + * fact(new stdClass())->hasMethod('__construct'); // Passes + * fact(new stdClass())->hasMethod('nonExistentMethod'); // Fails + * + * @param string $method The method name. + * @param string $message Optional custom error message. + * + * @return self Enables fluent chaining of assertion methods. + */ + public function hasMethod(string $method, string $message = ''): self + { + Assert::assertTrue(method_exists($this->variable, $method), $message ?: "Object does not have method '$method'."); + + return $this; + } +} \ No newline at end of file diff --git a/tests/FluentAssertions/Asserts/IsNotEmptyArray/IsNotEmptyArrayTest.php b/tests/FluentAssertions/Asserts/IsNotEmptyArray/IsNotEmptyArrayTest.php new file mode 100644 index 0000000..29c2bce --- /dev/null +++ b/tests/FluentAssertions/Asserts/IsNotEmptyArray/IsNotEmptyArrayTest.php @@ -0,0 +1,50 @@ +isNotEmptyArray(); + + // assert + $this->correctAssertionExecuted(); + } + + #[DataProvider('notIsNotEmptyArrayDataProvider')] + public function testNotIsNotEmptyArray(mixed $variable): void + { + // assert + $this->incorrectAssertionExpected(); + + // act + fact($variable)->isNotEmptyArray(); + } + + public static function isNotEmptyArrayDataProvider(): array + { + return [ + [[1]], + [[1, 2]], + [['a' => 'b']], + ]; + } + + public static function notIsNotEmptyArrayDataProvider(): array + { + return [ + [[]], + ]; + } +} \ No newline at end of file