diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 7649e16e..d09945a7 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -46,10 +46,10 @@ jobs:
run: composer install --prefer-dist ${{ matrix.composer-extra-arguments }}
- name: Run Unit Tests
- run: ./vendor/phpunit/phpunit/phpunit --testsuite=unit
+ run: ./vendor/bin/phpunit --testsuite=unit
- name: Run Integration Tests
- run: ./vendor/phpunit/phpunit/phpunit --testsuite=integration
+ run: ./vendor/bin/pest --testsuite=feature --compact
code-coverage:
name: Code coverage
@@ -78,7 +78,7 @@ jobs:
run: composer install --prefer-dist ${{ matrix.composer-extra-arguments }}
- name: Generating Code Coverage Report
- run: ./vendor/phpunit/phpunit/phpunit --coverage-clover=coverage.xml
+ run: ./vendor/bin/pest --compact --coverage-clover=coverage.xml
- name: Send Code Coverage Report to Codecov.io
uses: codecov/codecov-action@v5
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..34ce2c5a
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,6 @@
+coverage:
+ status:
+ project:
+ default:
+ target: 95% # the required coverage value
+ threshold: 1% # the leniency in hitting the target
diff --git a/composer.json b/composer.json
index 0fae6ab9..5a7f30fa 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,7 @@
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
+ "pestphp/pest-plugin": true,
"phpstan/extension-installer": true
}
},
@@ -29,6 +30,7 @@
"malukenho/docheader": "^1.0",
"mikey179/vfsstream": "^1.6",
"nette/php-generator": "^4.1",
+ "pestphp/pest": "^2.36",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0",
@@ -60,23 +62,20 @@
"Respect\\Validation\\": "tests/unit/",
"Respect\\Validation\\Test\\": "tests/library/"
},
- "files": [
- "tests/integration/lib/aliases.php",
- "tests/integration/lib/helpers.php"
- ]
+ "files": ["tests/aliases.php"]
},
"scripts": {
"docheader": "vendor/bin/docheader check library/ tests/",
"phpcs": "vendor/bin/phpcs",
"phpstan": "vendor/bin/phpstan analyze",
- "phpunit": "vendor/bin/phpunit",
- "phpunit-integration": "vendor/bin/phpunit --testsuite=integration",
- "phpunit-unit": "vendor/bin/phpunit --testsuite=unit",
+ "phpunit": "vendor/bin/phpunit --testsuite=unit",
+ "pest": "vendor/bin/pest --testsuite=feature --compact",
"qa": [
"@docheader",
"@phpcs",
"@phpstan",
- "@phpunit"
+ "@phpunit",
+ "@pest"
]
}
}
diff --git a/library/Transformers/DeprecatedAttribute.php b/library/Transformers/DeprecatedAttribute.php
index aef8e819..050880a6 100644
--- a/library/Transformers/DeprecatedAttribute.php
+++ b/library/Transformers/DeprecatedAttribute.php
@@ -48,7 +48,7 @@ final class DeprecatedAttribute implements Transformer
if ($name === 'property') {
trigger_error(
- $firstMessage . ' Use property() without it.',
+ $firstMessage . ' Use property() instead.',
E_USER_DEPRECATED
);
}
diff --git a/library/Transformers/DeprecatedKey.php b/library/Transformers/DeprecatedKey.php
index 224178da..e2c55e05 100644
--- a/library/Transformers/DeprecatedKey.php
+++ b/library/Transformers/DeprecatedKey.php
@@ -52,7 +52,7 @@ final class DeprecatedKey implements Transformer
if ($name === 'key') {
trigger_error(
'Calling key() with a third parameter has been deprecated, ' .
- 'and will be not be allowed in the next major version. Use key() without it the third parameter.',
+ 'and will be not be allowed in the next major version. Use key() without the third parameter.',
E_USER_DEPRECATED
);
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 3257a8c0..299b131e 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -16,7 +16,11 @@
- tests/integration/
+ tests/feature/
+
+
+ tests/Pest.php
+ tests/feature
@@ -26,7 +30,4 @@
-
- tests/integration/
-
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 60392621..43e2a269 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -7,28 +7,32 @@ parameters:
# Why: SimpleXMLElement is weird and doesn't implement anything ArrayAccess-like
message: '/Instanceof between mixed and SimpleXMLElement will always evaluate to false\./'
path: library/Rules/ArrayVal.php
+ - message: '/Call to an undefined method .+::skip\(\)/'
+ path: tests/feature
+ - message: '/Call to an undefined method .+::expectException\(\)/'
+ path: tests/Pest.php
- message: '/Call to deprecated method optional\(\).+/'
- path: tests/integration/transformers/aliases.phpt
+ path: tests/feature/Transformers/AliasesTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::(min|max)Age\(\)./'
- path: tests/integration/transformers/deprecated_age.phpt
+ path: tests/feature/Transformers/DeprecatedAgeTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::attribute\(\)./'
- path: tests/integration/transformers/deprecated_attribute.phpt
+ path: tests/feature/Transformers/DeprecatedAttributeTest.php
- message: '/Static method Respect\\Validation\\Mixins\\StaticValidator::key\(\) invoked with \d parameters?, 2 required/'
- path: tests/integration/transformers/deprecated_key.phpt
+ path: tests/feature/Transformers/DeprecatedKeyTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::keyNested\(\)./'
- path: tests/integration/transformers/deprecated_keyNested.phpt
+ path: tests/feature/Transformers/DeprecatedKeyNestedTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::keyValue\(\)./'
- path: tests/integration/transformers/deprecated_keyValue.phpt
+ path: tests/feature/Transformers/DeprecatedKeyValueTest.php
- message: '/Parameter #1 \$rule of static method Respect\\Validation\\Mixins\\StaticValidator::length\(\) expects Respect\\Validation\\Rule.+/'
- path: tests/integration/transformers/deprecated_length.phpt
+ path: tests/feature/Transformers/DeprecatedLengthTest.php
- message: '/Static method Respect\\Validation\\Mixins\\StaticValidator::length\(\) invoked with \d parameters, 1 required/'
- path: tests/integration/transformers/deprecated_length.phpt
+ path: tests/feature/Transformers/DeprecatedLengthTest.php
- message: '/Parameter #1 \$rule of static method Respect\\Validation\\Mixins\\StaticValidator::max\(\) expects Respect\\Validation\\Rule.+/'
- path: tests/integration/transformers/deprecated_max.phpt
+ path: tests/feature/Transformers/DeprecatedMaxTest.php
- message: '/Parameter #1 \$rule of static method Respect\\Validation\\Mixins\\StaticValidator::min\(\) expects Respect\\Validation\\Rule.+/'
- path: tests/integration/transformers/deprecated_min.phpt
+ path: tests/feature/Transformers/DeprecatedMinTest.php
- message: '/Call to an undefined static method Respect\\Validation\\Validator::type\(\)./'
- path: tests/integration/transformers/deprecated_type.phpt
+ path: tests/feature/Transformers/DeprecatedTypeTest.php
- message: '/Method .+\\TestingStringifier::stringify\(\) never returns null so it can be removed from the return type./'
path: tests/library/Message/TestingStringifier.php
- message: '/Parameter #1 \$messages of class .+\\ArrayTranslator constructor expects array, array given./'
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 949e8c27..644ad9fb 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -11,8 +11,8 @@
tests/unit/
-
- tests/integration/
+
+ ./tests/feature
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 00000000..22ac312b
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,104 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Exceptions\ValidationException;
+use Symfony\Component\VarExporter\VarExporter;
+
+use function PHPUnit\Framework\assertStringMatchesFormat;
+
+/** @param array $messages */
+function expectAll(callable $callback, string $message, string $fullMessage, array $messages): Closure
+{
+ return function () use ($callback, $message, $fullMessage, $messages): void {
+ try {
+ $callback();
+ test()->expectException(ValidationException::class);
+ } catch (ValidationException $e) {
+ expect($e->getMessage())->toBe($message)
+ ->and($e->getFullMessage())->toBe($fullMessage)
+ ->and($e->getMessages())->toBe($messages);
+ }
+ };
+}
+
+/** @param array $messages */
+function expectAllToMatch(callable $callback, string $message, string $fullMessage, array $messages): Closure
+{
+ return function () use ($callback, $message, $fullMessage, $messages): void {
+ try {
+ $callback();
+ test()->expectException(ValidationException::class);
+ } catch (ValidationException $e) {
+ assertStringMatchesFormat($message, $e->getMessage(), 'Validation message does not match');
+ assertStringMatchesFormat($fullMessage, $e->getFullMessage(), 'Validation full message does not match');
+ assertStringMatchesFormat(
+ VarExporter::export($messages),
+ VarExporter::export($e->getMessages()),
+ 'Validation messages do not match'
+ );
+ }
+ };
+}
+
+function expectMessage(callable $callback, string $message): Closure
+{
+ return function () use ($callback, $message): void {
+ try {
+ $callback();
+ test()->expectException(ValidationException::class);
+ } catch (ValidationException $e) {
+ expect($e->getMessage())->toBe($message, 'Validation message does not match');
+ }
+ };
+}
+
+function expectFullMessage(callable $callback, string $fullMessage): Closure
+{
+ return function () use ($callback, $fullMessage): void {
+ try {
+ $callback();
+ test()->expectException(ValidationException::class);
+ } catch (ValidationException $exception) {
+ expect($exception->getFullMessage())->toBe($fullMessage, 'Validation full message does not match');
+ }
+ };
+}
+
+/** @param array $messages */
+function expectMessages(callable $callback, array $messages): Closure
+{
+ return function () use ($callback, $messages): void {
+ try {
+ $callback();
+ test()->expectException(ValidationException::class);
+ } catch (ValidationException $exception) {
+ expect($exception->getMessages())->toBe($messages, 'Validation messages do not match');
+ }
+ };
+}
+
+function expectMessageAndError(Closure $callback, string $message, string $error): Closure
+{
+ return function () use ($callback, $message, $error): void {
+ $lastError = null;
+ set_error_handler(static function (int $errno, string $errstr) use (&$lastError): bool {
+ $lastError = $errstr;
+
+ return true;
+ });
+ try {
+ $callback();
+ test()->expectException(ValidationException::class);
+ } catch (ValidationException $e) {
+ expect($e->getMessage())->toBe($message, 'Validation message does not match');
+ }
+ restore_error_handler();
+ expect($lastError)->toBe($error);
+ };
+}
diff --git a/tests/integration/lib/aliases.php b/tests/aliases.php
similarity index 100%
rename from tests/integration/lib/aliases.php
rename to tests/aliases.php
diff --git a/tests/integration/assert-with-keys.phpt b/tests/feature/AssertWithKeysTest.php
similarity index 54%
rename from tests/integration/assert-with-keys.phpt
rename to tests/feature/AssertWithKeysTest.php
index 7b40f534..d4a756fd 100644
--- a/tests/integration/assert-with-keys.phpt
+++ b/tests/feature/AssertWithKeysTest.php
@@ -1,10 +1,14 @@
---FILE--
+ * SPDX-License-Identifier: MIT
+ */
-exceptionFullMessage(static function (): void {
- v::create()
+declare(strict_types=1);
+
+test('Scenario #1', expectFullMessage(
+ fn() => v::create()
->key(
'mysql',
v::create()
@@ -31,18 +35,18 @@ exceptionFullMessage(static function (): void {
'user' => 42,
'password' => 42,
],
- ]);
-});
-?>
---EXPECT--
-- All of the required rules must pass for the given data
- - All of the required rules must pass for mysql
- - host must be a string
- - user must be present
- - password must be present
- - schema must be a string
- - All of the required rules must pass for postgresql
- - host must be present
- - user must be a string
- - password must be a string
- - schema must be present
\ No newline at end of file
+ ]),
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for the given data
+ - All of the required rules must pass for mysql
+ - host must be a string
+ - user must be present
+ - password must be present
+ - schema must be a string
+ - All of the required rules must pass for postgresql
+ - host must be present
+ - user must be a string
+ - password must be a string
+ - schema must be present
+ FULL_MESSAGE,
+));
diff --git a/tests/feature/AssertWithPropertiesTest.php b/tests/feature/AssertWithPropertiesTest.php
new file mode 100644
index 00000000..1eb35c59
--- /dev/null
+++ b/tests/feature/AssertWithPropertiesTest.php
@@ -0,0 +1,54 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectFullMessage(
+ function (): void {
+ $array = [
+ 'mysql' => [
+ 'host' => 42,
+ 'user' => 'user',
+ 'password' => 'password',
+ 'schema' => 'schema',
+ ],
+ 'postgresql' => [
+ 'host' => 'host',
+ 'user' => 42,
+ 'password' => 'password',
+ 'schema' => 'schema',
+ ],
+ ];
+ $object = json_decode((string) json_encode($array));
+ v::create()
+ ->property(
+ 'mysql',
+ v::create()
+ ->property('host', v::stringType())
+ ->property('user', v::stringType())
+ ->property('password', v::stringType())
+ ->property('schema', v::stringType())
+ )
+ ->property(
+ 'postgresql',
+ v::create()
+ ->property('host', v::stringType())
+ ->property('user', v::stringType())
+ ->property('password', v::stringType())
+ ->property('schema', v::stringType())
+ )
+ ->setName('the given data')
+ ->assert($object);
+ },
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for the given data
+ - These rules must pass for mysql
+ - host must be a string
+ - These rules must pass for postgresql
+ - user must be a string
+ FULL_MESSAGE,
+));
diff --git a/tests/feature/AssertWithTemplatesTest.php b/tests/feature/AssertWithTemplatesTest.php
new file mode 100644
index 00000000..bcee28e1
--- /dev/null
+++ b/tests/feature/AssertWithTemplatesTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Template as a string in the chain', expectAll(
+ fn() => v::alwaysInvalid()->setTemplate('My string template in the chain')->assert(1),
+ 'My string template in the chain',
+ '- My string template in the chain',
+ ['alwaysInvalid' => 'My string template in the chain']
+));
+
+test('Template as an array in the chain', expectAll(
+ fn() => v::alwaysInvalid()->setTemplates(['alwaysInvalid' => 'My array template in the chain'])->assert(1),
+ 'My array template in the chain',
+ '- My array template in the chain',
+ ['alwaysInvalid' => 'My array template in the chain']
+));
+
+test('Runtime template as string', expectAll(
+ fn() => v::alwaysInvalid()->assert(1, 'My runtime template as string'),
+ 'My runtime template as string',
+ '- My runtime template as string',
+ ['alwaysInvalid' => 'My runtime template as string']
+));
+
+test('Runtime template as an array', expectAll(
+ fn() => v::alwaysInvalid()->assert(1, ['alwaysInvalid' => 'My runtime template an array']),
+ 'My runtime template an array',
+ '- My runtime template an array',
+ ['alwaysInvalid' => 'My runtime template an array']
+));
diff --git a/tests/feature/DoNotRelyOnNestedValidationExceptionInterfaceForCheckTest.php b/tests/feature/DoNotRelyOnNestedValidationExceptionInterfaceForCheckTest.php
new file mode 100644
index 00000000..18c10c11
--- /dev/null
+++ b/tests/feature/DoNotRelyOnNestedValidationExceptionInterfaceForCheckTest.php
@@ -0,0 +1,15 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+test('Scenario #1', expectMessage(
+ fn() => Validator::alnum('__')->lengthBetween(1, 15)->noWhitespace()->assert('really messed up screen#name'),
+ '"really messed up screen#name" must contain only letters (a-z), digits (0-9), and "__"',
+));
diff --git a/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php b/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php
new file mode 100644
index 00000000..67f44709
--- /dev/null
+++ b/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php
@@ -0,0 +1,19 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+test('Scenario #1', expectFullMessage(
+ fn() => Validator::stringType()->lengthBetween(2, 15)->assert(0),
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 0
+ - 0 must be a string
+ - The length of 0 must be between 2 and 15
+ FULL_MESSAGE,
+));
diff --git a/tests/feature/GetMessagesShouldIncludeAllValidationMessagesInAChainTest.php b/tests/feature/GetMessagesShouldIncludeAllValidationMessagesInAChainTest.php
new file mode 100644
index 00000000..15f68f48
--- /dev/null
+++ b/tests/feature/GetMessagesShouldIncludeAllValidationMessagesInAChainTest.php
@@ -0,0 +1,29 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+date_default_timezone_set('UTC');
+
+test('Scenario #1', expectMessages(
+ function (): void {
+ Validator::create()
+ ->key('username', Validator::lengthBetween(2, 32))->key('birthdate', Validator::dateTime())
+ ->key('password', Validator::notEmpty())
+ ->key('email', Validator::email())
+ ->assert(['username' => 'u', 'birthdate' => 'Not a date', 'password' => '']);
+ },
+ [
+ '__root__' => 'All of the required rules must pass for `["username": "u", "birthdate": "Not a date", "password": ""]`',
+ 'username' => 'The length of username must be between 2 and 32',
+ 'birthdate' => 'birthdate must be a valid date/time',
+ 'password' => 'password must not be empty',
+ 'email' => 'email must be present',
+ ],
+));
diff --git a/tests/feature/GetMessagesTest.php b/tests/feature/GetMessagesTest.php
new file mode 100644
index 00000000..641d1e7c
--- /dev/null
+++ b/tests/feature/GetMessagesTest.php
@@ -0,0 +1,39 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessages(
+ fn() => v::create()
+ ->key('mysql', v::create()
+ ->key('host', v::stringType())
+ ->key('user', v::stringType())->key('password', v::stringType())
+ ->key('schema', v::stringType()))
+ ->key('postgresql', v::create()
+ ->key('host', v::stringType())
+ ->key('user', v::stringType())
+ ->key('password', v::stringType())
+ ->key('schema', v::stringType()))
+ ->assert(['mysql' => ['host' => 42, 'schema' => 42], 'postgresql' => ['user' => 42, 'password' => 42]]),
+ [
+ '__root__' => 'All of the required rules must pass for `["mysql": ["host": 42, "schema": 42], "postgresql": ["user": 42, "password": 42]]`',
+ 'mysql' => [
+ '__root__' => 'All of the required rules must pass for mysql',
+ 'host' => 'host must be a string',
+ 'user' => 'user must be present',
+ 'password' => 'password must be present',
+ 'schema' => 'schema must be a string',
+ ],
+ 'postgresql' => [
+ '__root__' => 'All of the required rules must pass for postgresql',
+ 'host' => 'host must be present',
+ 'user' => 'user must be a string',
+ 'password' => 'password must be a string',
+ 'schema' => 'schema must be present',
+ ],
+ ],
+));
diff --git a/tests/integration/get_messages_with_replacements.phpt b/tests/feature/GetMessagesWithReplacementsTest.php
similarity index 57%
rename from tests/integration/get_messages_with_replacements.phpt
rename to tests/feature/GetMessagesWithReplacementsTest.php
index bcc05fed..2d1022ee 100644
--- a/tests/integration/get_messages_with_replacements.phpt
+++ b/tests/feature/GetMessagesWithReplacementsTest.php
@@ -1,10 +1,14 @@
---FILE--
+ * SPDX-License-Identifier: MIT
+ */
-exceptionMessages(
- static function (): void {
+declare(strict_types=1);
+
+test('Scenario #1', expectMessages(
+ function (): void {
v::create()
->key(
'mysql',
@@ -43,24 +47,22 @@ exceptionMessages(
],
]
);
- }
-);
-?>
---EXPECT--
-[
- '__root__' => 'All of the required rules must pass for `["mysql": ["host": 42, "schema": 42], "postgresql": ["user": 42, "password": 42]]`',
- 'mysql' => [
- '__root__' => 'All of the required rules must pass for mysql',
- 'host' => '`host` should be a MySQL host',
- 'user' => 'Value should be a MySQL username',
- 'password' => 'password must be present',
- 'schema' => 'schema must be a string',
+ },
+ [
+ '__root__' => 'All of the required rules must pass for `["mysql": ["host": 42, "schema": 42], "postgresql": ["user": 42, "password": 42]]`',
+ 'mysql' => [
+ '__root__' => 'All of the required rules must pass for mysql',
+ 'host' => '`host` should be a MySQL host',
+ 'user' => 'Value should be a MySQL username',
+ 'password' => 'password must be present',
+ 'schema' => 'schema must be a string',
+ ],
+ 'postgresql' => [
+ '__root__' => 'All of the required rules must pass for postgresql',
+ 'host' => 'host must be present',
+ 'user' => 'user must be a string',
+ 'password' => 'password must be a string',
+ 'schema' => 'You must provide a valid PostgreSQL schema',
+ ],
],
- 'postgresql' => [
- '__root__' => 'All of the required rules must pass for postgresql',
- 'host' => 'host must be present',
- 'user' => 'user must be a string',
- 'password' => 'password must be a string',
- 'schema' => 'You must provide a valid PostgreSQL schema',
- ],
-]
\ No newline at end of file
+));
diff --git a/tests/feature/Issues/Issue1033Test.php b/tests/feature/Issues/Issue1033Test.php
new file mode 100644
index 00000000..9914ebac
--- /dev/null
+++ b/tests/feature/Issues/Issue1033Test.php
@@ -0,0 +1,25 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/1033', expectAll(
+ fn() => v::each(v::equals(1))->assert(['A', 'B', 'B']),
+ '"A" must be equal to 1',
+ <<<'FULL_MESSAGE'
+ - Each item in `["A", "B", "B"]` must be valid
+ - "A" must be equal to 1
+ - "B" must be equal to 1
+ - "B" must be equal to 1
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in `["A", "B", "B"]` must be valid',
+ 'equals.1' => '"A" must be equal to 1',
+ 'equals.2' => '"B" must be equal to 1',
+ 'equals.3' => '"B" must be equal to 1',
+ ]
+));
diff --git a/tests/feature/Issues/Issue1244Test.php b/tests/feature/Issues/Issue1244Test.php
new file mode 100644
index 00000000..a4744004
--- /dev/null
+++ b/tests/feature/Issues/Issue1244Test.php
@@ -0,0 +1,15 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/1244', expectAll(
+ fn() => v::key('firstname', v::notBlank()->setName('First Name'))->assert([]),
+ 'First Name must be present',
+ '- First Name must be present',
+ ['firstname' => 'First Name must be present']
+));
diff --git a/tests/feature/Issues/Issue1289Test.php b/tests/feature/Issues/Issue1289Test.php
new file mode 100644
index 00000000..74722ef6
--- /dev/null
+++ b/tests/feature/Issues/Issue1289Test.php
@@ -0,0 +1,67 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Rules\ArrayType;
+use Respect\Validation\Rules\BoolType;
+use Respect\Validation\Rules\Each;
+use Respect\Validation\Rules\KeyOptional;
+use Respect\Validation\Rules\OneOf;
+use Respect\Validation\Rules\StringType;
+use Respect\Validation\Rules\StringVal;
+use Respect\Validation\Validator;
+
+test('https://github.com/Respect/Validation/issues/1289', expectAll(
+ fn() => Validator::create(
+ new Each(
+ Validator::create(
+ new KeyOptional(
+ 'default',
+ new OneOf(
+ new StringType(),
+ new BoolType()
+ )
+ ),
+ new KeyOptional(
+ 'description',
+ new StringVal(),
+ ),
+ new KeyOptional(
+ 'children',
+ new ArrayType(),
+ )
+ )
+ )
+ )
+ ->assert([
+ [
+ 'default' => 2,
+ 'description' => [],
+ 'children' => ['nope'],
+ ],
+ ]),
+ 'default must be a string',
+ <<<'FULL_MESSAGE'
+ - These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`
+ - Only one of these rules must pass for default
+ - default must be a string
+ - default must be a boolean
+ - description must be a string value
+ FULL_MESSAGE,
+ [
+ 'allOf' => [
+ '__root__' => 'These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`',
+ 'default' => [
+ '__root__' => 'Only one of these rules must pass for default',
+ 'stringType' => 'default must be a string',
+ 'boolType' => 'default must be a boolean',
+ ],
+ 'description' => 'description must be a string value',
+ ],
+ ]
+));
diff --git a/tests/feature/Issues/Issue1333Test.php b/tests/feature/Issues/Issue1333Test.php
new file mode 100644
index 00000000..bf1e357a
--- /dev/null
+++ b/tests/feature/Issues/Issue1333Test.php
@@ -0,0 +1,23 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/1333', expectAll(
+ fn() => v::noWhitespace()->email()->setName('User Email')->assert('not email'),
+ 'User Email must not contain whitespaces',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for User Email
+ - User Email must not contain whitespaces
+ - User Email must be a valid email address
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for User Email',
+ 'noWhitespace' => 'User Email must not contain whitespaces',
+ 'email' => 'User Email must be a valid email address',
+ ]
+));
diff --git a/tests/feature/Issues/Issue1334Test.php b/tests/feature/Issues/Issue1334Test.php
new file mode 100644
index 00000000..253115eb
--- /dev/null
+++ b/tests/feature/Issues/Issue1334Test.php
@@ -0,0 +1,49 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/1334', expectAll(
+ function (): void {
+ v::notEmpty()->iterableType()->each(
+ v::key('street', v::stringType()->notEmpty())
+ ->key('region', v::stringType()->notEmpty())
+ ->key('country', v::stringType()->notEmpty())
+ ->keyOptional('other', v::nullOr(v::notEmpty()->stringType()))
+ )->assert(
+ [
+ ['region' => 'Oregon', 'country' => 'USA', 'other' => 123],
+ ['street' => '', 'region' => 'Oregon', 'country' => 'USA'],
+ ['street' => 123, 'region' => 'Oregon', 'country' => 'USA'],
+ ]
+ );
+ },
+ 'street must be present',
+ <<<'FULL_MESSAGE'
+ - Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid
+ - These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`
+ - street must be present
+ - These rules must pass for other
+ - other must be a string or must be null
+ - These rules must pass for `["street": "", "region": "Oregon", "country": "USA"]`
+ - street must not be empty
+ - These rules must pass for `["street": 123, "region": "Oregon", "country": "USA"]`
+ - street must be a string
+ FULL_MESSAGE,
+ [
+ 'each' => [
+ '__root__' => 'Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid',
+ 'allOf.1' => [
+ '__root__' => 'These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`',
+ 'street' => 'street must be present',
+ 'other' => 'other must be a string or must be null',
+ ],
+ 'allOf.2' => 'street must not be empty',
+ 'allOf.3' => 'street must be a string',
+ ],
+ ]
+));
diff --git a/tests/feature/Issues/Issue1348Test.php b/tests/feature/Issues/Issue1348Test.php
new file mode 100644
index 00000000..21becf34
--- /dev/null
+++ b/tests/feature/Issues/Issue1348Test.php
@@ -0,0 +1,82 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+$cars = [
+ ['manufacturer' => 'Honda', 'model' => 'Accord'],
+ ['manufacturer' => 'Toyota', 'model' => 'Rav4'],
+ ['manufacturer' => 'Ford', 'model' => 'not real'],
+ ['manufacturer' => 'Honda', 'model' => 'not valid'],
+];
+
+test('https://github.com/Respect/Validation/issues/1289', expectAll(
+ fn() => v::arrayType()->each(
+ v::oneOf(
+ v::key('manufacturer', v::equals('Honda'))->key('model', v::in(['Accord', 'Fit'])),
+ v::key('manufacturer', v::equals('Toyota'))->key('model', v::in(['Rav4', 'Camry'])),
+ v::key('manufacturer', v::equals('Ford'))->key('model', Validator::in(['F150', 'Bronco']))
+ )
+ )->assert($cars),
+ 'manufacturer must be equal to "Honda"',
+ <<<'FULL_MESSAGE'
+ - Each item in `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]` must be valid
+ - Only one of these rules must pass for `["manufacturer": "Ford", "model": "not real"]`
+ - All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`
+ - manufacturer must be equal to "Honda"
+ - model must be in `["Accord", "Fit"]`
+ - All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`
+ - manufacturer must be equal to "Toyota"
+ - model must be in `["Rav4", "Camry"]`
+ - These rules must pass for `["manufacturer": "Ford", "model": "not real"]`
+ - model must be in `["F150", "Bronco"]`
+ - Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
+ - These rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
+ - model must be in `["Accord", "Fit"]`
+ - All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
+ - manufacturer must be equal to "Toyota"
+ - model must be in `["Rav4", "Camry"]`
+ - All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
+ - manufacturer must be equal to "Ford"
+ - model must be in `["F150", "Bronco"]`
+ FULL_MESSAGE,
+ [
+ 'each' => [
+ '__root__' => 'Each item in `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]` must be valid',
+ 'oneOf.3' => [
+ '__root__' => 'Only one of these rules must pass for `["manufacturer": "Ford", "model": "not real"]`',
+ 'allOf.1' => [
+ '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`',
+ 'manufacturer' => 'manufacturer must be equal to "Honda"',
+ 'model' => 'model must be in `["Accord", "Fit"]`',
+ ],
+ 'allOf.2' => [
+ '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`',
+ 'manufacturer' => 'manufacturer must be equal to "Toyota"',
+ 'model' => 'model must be in `["Rav4", "Camry"]`',
+ ],
+ 'allOf.3' => 'model must be in `["F150", "Bronco"]`',
+ ],
+ 'oneOf.4' => [
+ '__root__' => 'Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`',
+ 'allOf.1' => 'model must be in `["Accord", "Fit"]`',
+ 'allOf.2' => [
+ '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`',
+ 'manufacturer' => 'manufacturer must be equal to "Toyota"',
+ 'model' => 'model must be in `["Rav4", "Camry"]`',
+ ],
+ 'allOf.3' => [
+ '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`',
+ 'manufacturer' => 'manufacturer must be equal to "Ford"',
+ 'model' => 'model must be in `["F150", "Bronco"]`',
+ ],
+ ],
+ ],
+ ]
+));
diff --git a/tests/feature/Issues/Issue1376Test.php b/tests/feature/Issues/Issue1376Test.php
new file mode 100644
index 00000000..cc724441
--- /dev/null
+++ b/tests/feature/Issues/Issue1376Test.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/1376', expectAll(
+ fn() => v::create()
+ ->property('title', v::lengthBetween(2, 3)->stringType())
+ ->property('description', v::stringType())
+ ->property('author', v::intType()->lengthBetween(1, 2))
+ ->property('user', v::intVal()->lengthBetween(1, 2))
+ ->assert((object) ['author' => 'foo']),
+ 'title must be present',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for `stdClass { +$author="foo" }`
+ - title must be present
+ - description must be present
+ - All of the required rules must pass for author
+ - author must be an integer
+ - The length of author must be between 1 and 2
+ - user must be present
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for `stdClass { +$author="foo" }`',
+ 'title' => 'title must be present',
+ 'description' => 'description must be present',
+ 'author' => [
+ '__root__' => 'All of the required rules must pass for author',
+ 'intType' => 'author must be an integer',
+ 'lengthBetween' => 'The length of author must be between 1 and 2',
+ ],
+ 'user' => 'user must be present',
+ ]
+));
diff --git a/tests/feature/Issues/Issue1469Test.php b/tests/feature/Issues/Issue1469Test.php
new file mode 100644
index 00000000..e9c3709d
--- /dev/null
+++ b/tests/feature/Issues/Issue1469Test.php
@@ -0,0 +1,53 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/1469', expectAll(
+ function (): void {
+ $data = [
+ 'order_items' => [
+ [
+ 'product_title' => 'test',
+ 'quantity' => 'test',
+ ],
+ [
+ 'product_title2' => 'test',
+ ],
+ ],
+ ];
+
+ v::arrayVal()->keySet(
+ v::key('order_items', v::arrayVal()->each(v::keySet(
+ v::key('product_title', v::stringVal()->notEmpty()),
+ v::key('quantity', v::intVal()->notEmpty()),
+ ))->notEmpty()),
+ )->assert($data);
+ },
+ 'quantity must be an integer value',
+ <<<'FULL_MESSAGE'
+ - Each item in order_items must be valid
+ - order_items validation failed
+ - quantity must be an integer value
+ - order_items contains both missing and extra keys
+ - product_title must be present
+ - quantity must be present
+ - product_title2 must not be present
+ FULL_MESSAGE,
+ [
+ 'keySet' => [
+ '__root__' => 'Each item in order_items must be valid',
+ 'keySet.1' => 'quantity must be an integer value',
+ 'keySet.2' => [
+ '__root__' => 'order_items contains both missing and extra keys',
+ 'product_title' => 'product_title must be present',
+ 'quantity' => 'quantity must be present',
+ 'product_title2' => 'product_title2 must not be present',
+ ],
+ ],
+ ]
+));
diff --git a/tests/feature/Issues/Issue1477Test.php b/tests/feature/Issues/Issue1477Test.php
new file mode 100644
index 00000000..fe8c09df
--- /dev/null
+++ b/tests/feature/Issues/Issue1477Test.php
@@ -0,0 +1,27 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Rules\Core\Simple;
+
+test('https://github.com/Respect/Validation/issues/1477', expectAll(
+ function (): void {
+ v::key(
+ 'Address',
+ (new class extends Simple {
+ protected function isValid(mixed $input): bool
+ {
+ return false;
+ }
+ })->setTemplate('{{name}} is not good!')
+ )->assert(['Address' => 'cvejvn']);
+ },
+ 'Address is not good!',
+ '- Address is not good!',
+ ['Address' => 'Address is not good!']
+));
diff --git a/tests/feature/Issues/Issue179Test.php b/tests/feature/Issues/Issue179Test.php
new file mode 100644
index 00000000..e4d3fa4c
--- /dev/null
+++ b/tests/feature/Issues/Issue179Test.php
@@ -0,0 +1,37 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/179', expectAll(
+ function (): void {
+ $config = [
+ 'host' => 1,
+ 'password' => 'my_password',
+ 'schema' => 'my_schema',
+ ];
+
+ $validator = v::arrayType();
+ $validator->setName('Settings');
+ $validator->key('host', v::stringType());
+ $validator->key('user', v::stringType());
+ $validator->key('password', v::stringType());
+ $validator->key('schema', v::stringType());
+ $validator->assert($config);
+ },
+ 'host must be a string',
+ <<<'FULL_MESSAGE'
+ - These rules must pass for Settings
+ - host must be a string
+ - user must be present
+ FULL_MESSAGE,
+ [
+ '__root__' => 'These rules must pass for Settings',
+ 'host' => 'host must be a string',
+ 'user' => 'user must be present',
+ ]
+));
diff --git a/tests/feature/Issues/Issue425Test.php b/tests/feature/Issues/Issue425Test.php
new file mode 100644
index 00000000..096026b1
--- /dev/null
+++ b/tests/feature/Issues/Issue425Test.php
@@ -0,0 +1,20 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/425', expectAll(
+ function (): void {
+ $validator = v::create()
+ ->key('age', v::intType()->notEmpty()->noneOf(v::stringType(), v::arrayType()))
+ ->key('reference', v::stringType()->notEmpty()->lengthBetween(1, 50));
+ $validator->assert(['age' => 1]);
+ },
+ 'reference must be present',
+ '- reference must be present',
+ ['reference' => 'reference must be present']
+));
diff --git a/tests/feature/Issues/Issue446Test.php b/tests/feature/Issues/Issue446Test.php
new file mode 100644
index 00000000..c99c5469
--- /dev/null
+++ b/tests/feature/Issues/Issue446Test.php
@@ -0,0 +1,23 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+$arr = [
+ 'name' => 'w',
+ 'email' => 'hello@hello.com',
+];
+
+test('https://github.com/Respect/Validation/issues/446', expectAll(
+ fn() => v::create()
+ ->key('name', v::lengthBetween(2, 32))
+ ->key('email', v::email())
+ ->assert($arr),
+ 'The length of name must be between 2 and 32',
+ '- The length of name must be between 2 and 32',
+ ['name' => 'The length of name must be between 2 and 32']
+));
diff --git a/tests/feature/Issues/Issue619Test.php b/tests/feature/Issues/Issue619Test.php
new file mode 100644
index 00000000..375ae73f
--- /dev/null
+++ b/tests/feature/Issues/Issue619Test.php
@@ -0,0 +1,15 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/619', expectAll(
+ fn() => v::instance(stdClass::class)->setTemplate('invalid object')->assert('test'),
+ 'invalid object',
+ '- invalid object',
+ ['instance' => 'invalid object']
+));
diff --git a/tests/feature/Issues/Issue739Test.php b/tests/feature/Issues/Issue739Test.php
new file mode 100644
index 00000000..65204a56
--- /dev/null
+++ b/tests/feature/Issues/Issue739Test.php
@@ -0,0 +1,15 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/739', expectAll(
+ fn() => v::when(v::alwaysInvalid(), v::alwaysValid())->assert('foo'),
+ '"foo" is invalid',
+ '- "foo" is invalid',
+ ['alwaysInvalid' => '"foo" is invalid']
+));
diff --git a/tests/integration/issues/796.phpt b/tests/feature/Issues/Issue796Test.php
similarity index 56%
rename from tests/integration/issues/796.phpt
rename to tests/feature/Issues/Issue796Test.php
index fd5bce7b..a53f00da 100644
--- a/tests/integration/issues/796.phpt
+++ b/tests/feature/Issues/Issue796Test.php
@@ -1,10 +1,14 @@
---FILE--
+ * SPDX-License-Identifier: MIT
+ */
-exceptionAll('https://github.com/Respect/Validation/issues/796', static function (): void {
- v::create()
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/796', expectAll(
+ fn () => v::create()
->key(
'mysql',
v::create()
@@ -35,20 +39,18 @@ exceptionAll('https://github.com/Respect/Validation/issues/796', static function
'password' => 'password',
'schema' => 'schema',
],
- ]);
-});
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/796
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-host must be a string
-- All of the required rules must pass for the given data
- - These rules must pass for mysql
- - host must be a string
- - These rules must pass for postgresql
- - user must be a string
-[
- '__root__' => 'All of the required rules must pass for the given data',
- 'mysql' => 'host must be a string',
- 'postgresql' => 'user must be a string',
-]
+ ]),
+ 'host must be a string',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for the given data
+ - These rules must pass for mysql
+ - host must be a string
+ - These rules must pass for postgresql
+ - user must be a string
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for the given data',
+ 'mysql' => 'host must be a string',
+ 'postgresql' => 'user must be a string',
+ ]
+));
diff --git a/tests/feature/Issues/Issue799Test.php b/tests/feature/Issues/Issue799Test.php
new file mode 100644
index 00000000..2c5f3c39
--- /dev/null
+++ b/tests/feature/Issues/Issue799Test.php
@@ -0,0 +1,46 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Test\Stubs\CountableStub;
+
+$input = 'http://www.google.com/search?q=respect.github.com';
+
+test('https://github.com/Respect/Validation/issues/799 | #1', expectAll(
+ fn() => v::create()
+ ->call(
+ [new CountableStub(1), 'count'],
+ v::arrayVal()->key('scheme', v::startsWith('https'))
+ )
+ ->assert($input),
+ '1 must be an array value',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1
+ - 1 must be an array value
+ - scheme must be present
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1',
+ 'arrayVal' => '1 must be an array value',
+ 'scheme' => 'scheme must be present',
+ ]
+));
+
+test('https://github.com/Respect/Validation/issues/799 | #2', expectAll(
+ fn() => v::create()
+ ->call(
+ function ($url) {
+ return parse_url($url);
+ },
+ v::arrayVal()->key('scheme', v::startsWith('https'))
+ )
+ ->assert($input),
+ 'scheme must start with "https"',
+ '- scheme must start with "https"',
+ ['scheme' => 'scheme must start with "https"']
+));
diff --git a/tests/feature/Issues/Issue805Test.php b/tests/feature/Issues/Issue805Test.php
new file mode 100644
index 00000000..be708c0b
--- /dev/null
+++ b/tests/feature/Issues/Issue805Test.php
@@ -0,0 +1,15 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('https://github.com/Respect/Validation/issues/805', expectAll(
+ fn() => v::key('email', v::email()->setTemplate('WRONG EMAIL!!!!!!'))->assert(['email' => 'qwe']),
+ 'WRONG EMAIL!!!!!!',
+ '- WRONG EMAIL!!!!!!',
+ ['email' => 'WRONG EMAIL!!!!!!']
+));
diff --git a/tests/feature/KeysAsValidatorNamesTest.php b/tests/feature/KeysAsValidatorNamesTest.php
new file mode 100644
index 00000000..77757b3b
--- /dev/null
+++ b/tests/feature/KeysAsValidatorNamesTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+
+use Respect\Validation\Validator;
+
+test('Scenario #1', expectFullMessage(
+ function (): void {
+ Validator::create()
+ ->key('username', Validator::length(Validator::between(2, 32)))
+ ->key('birthdate', Validator::dateTime())
+ ->setName('User Subscription Form')
+ ->assert(['username' => '0', 'birthdate' => 'Whatever']);
+ },
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for User Subscription Form
+ - The length of username must be between 2 and 32
+ - birthdate must be a valid date/time
+ FULL_MESSAGE,
+));
diff --git a/tests/feature/NotShouldWorkWithBuilderTestTest.php b/tests/feature/NotShouldWorkWithBuilderTestTest.php
new file mode 100644
index 00000000..f694d5e1
--- /dev/null
+++ b/tests/feature/NotShouldWorkWithBuilderTestTest.php
@@ -0,0 +1,13 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test(
+ 'not() should work by builder',
+ fn() => expect(v::not(v::intVal())->isValid(10))->toBeFalse()
+);
diff --git a/tests/feature/NotWithRecursionTest.php b/tests/feature/NotWithRecursionTest.php
new file mode 100644
index 00000000..2f4f549d
--- /dev/null
+++ b/tests/feature/NotWithRecursionTest.php
@@ -0,0 +1,42 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::not(
+ v::not(
+ v::not(
+ v::not(
+ v::not(
+ v::intVal()->positive()
+ )
+ )
+ )
+ )
+ )->assert(2),
+ '2 must not be an integer value',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::not(
+ v::not(
+ v::not(
+ v::not(
+ v::not(
+ v::intVal()->positive()
+ )
+ )
+ )
+ )
+ )->assert(2),
+ <<<'FULL_MESSAGE'
+ - These rules must not pass for 2
+ - 2 must not be an integer value
+ - 2 must not be a positive number
+ FULL_MESSAGE,
+));
diff --git a/tests/feature/NotWithoutRecursionTest.php b/tests/feature/NotWithoutRecursionTest.php
new file mode 100644
index 00000000..7064fe29
--- /dev/null
+++ b/tests/feature/NotWithoutRecursionTest.php
@@ -0,0 +1,18 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+test('Scenario #1', expectMessage(
+ function (): void {
+ $validator = Validator::not(Validator::intVal()->positive());
+ $validator->assert(2);
+ },
+ '2 must not be an integer value',
+));
diff --git a/tests/feature/Readme/CustomMessagesTest.php b/tests/feature/Readme/CustomMessagesTest.php
new file mode 100644
index 00000000..1692eeb8
--- /dev/null
+++ b/tests/feature/Readme/CustomMessagesTest.php
@@ -0,0 +1,25 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessages(
+ fn() => v::alnum()
+ ->noWhitespace()
+ ->length(v::between(1, 15))
+ ->assert('really messed up screen#name', [
+ 'alnum' => '{{name}} must contain only letters and digits',
+ 'noWhitespace' => '{{name}} cannot contain spaces',
+ 'length' => '{{name}} must not have more than 15 chars',
+ ]),
+ [
+ '__root__' => 'All of the required rules must pass for "really messed up screen#name"',
+ 'alnum' => '"really messed up screen#name" must contain only letters and digits',
+ 'noWhitespace' => '"really messed up screen#name" cannot contain spaces',
+ 'lengthBetween' => 'The length of "really messed up screen#name" must be between 1 and 15',
+ ],
+));
diff --git a/tests/feature/Readme/ExampleTest.php b/tests/feature/Readme/ExampleTest.php
new file mode 100644
index 00000000..0cbb887b
--- /dev/null
+++ b/tests/feature/Readme/ExampleTest.php
@@ -0,0 +1,18 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectFullMessage(
+ fn() => v::alnum()->noWhitespace()->lengthBetween(1, 15)->assert('really messed up screen#name'),
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for "really messed up screen#name"
+ - "really messed up screen#name" must contain only letters (a-z) and digits (0-9)
+ - "really messed up screen#name" must not contain whitespaces
+ - The length of "really messed up screen#name" must be between 1 and 15
+ FULL_MESSAGE,
+));
diff --git a/tests/feature/Readme/GettingMessagesAsAnArrayTest.php b/tests/feature/Readme/GettingMessagesAsAnArrayTest.php
new file mode 100644
index 00000000..b2fc6599
--- /dev/null
+++ b/tests/feature/Readme/GettingMessagesAsAnArrayTest.php
@@ -0,0 +1,18 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessages(
+ fn() => v::alnum()->noWhitespace()->lengthBetween(1, 15)->assert('really messed up screen#name'),
+ [
+ '__root__' => 'All of the required rules must pass for "really messed up screen#name"',
+ 'alnum' => '"really messed up screen#name" must contain only letters (a-z) and digits (0-9)',
+ 'noWhitespace' => '"really messed up screen#name" must not contain whitespaces',
+ 'lengthBetween' => 'The length of "really messed up screen#name" must be between 1 and 15',
+ ],
+));
diff --git a/tests/feature/Rules/AllOfTest.php b/tests/feature/Rules/AllOfTest.php
new file mode 100644
index 00000000..867942f7
--- /dev/null
+++ b/tests/feature/Rules/AllOfTest.php
@@ -0,0 +1,71 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Two rules', expectAll(
+ fn() => v::allOf(v::intType(), v::negative())->assert('2'),
+ '"2" must be an integer',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for "2"
+ - "2" must be an integer
+ - "2" must be a negative number
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for "2"',
+ 'intType' => '"2" must be an integer',
+ 'negative' => '"2" must be a negative number',
+ ]
+));
+
+test('Wrapped by "not"', expectAll(
+ fn() => v::not(v::allOf(v::intType(), v::positive()))->assert(3),
+ '3 must not be an integer',
+ <<<'FULL_MESSAGE'
+ - These rules must not pass for 3
+ - 3 must not be an integer
+ - 3 must not be a positive number
+ FULL_MESSAGE,
+ [
+ '__root__' => 'These rules must not pass for 3',
+ 'intType' => '3 must not be an integer',
+ 'positive' => '3 must not be a positive number',
+ ]
+));
+
+test('Wrapping "not"', expectAll(
+ fn() => v::allOf(v::not(v::intType()), v::greaterThan(2))->assert(4),
+ '4 must not be an integer',
+ '- 4 must not be an integer',
+ ['notIntType' => '4 must not be an integer']
+));
+
+test('With a single template', expectAll(
+ fn() => v::allOf(v::stringType(), v::arrayType())->assert(5, 'This is a single template'),
+ 'This is a single template',
+ '- This is a single template',
+ ['allOf' => 'This is a single template']
+));
+
+test('With multiple templates', expectAll(
+ fn() => v::allOf(v::stringType(), v::uppercase())->assert(5, [
+ '__root__' => 'Two things are wrong',
+ 'stringType' => 'Template for "stringType"',
+ 'uppercase' => 'Template for "uppercase"',
+ ]),
+ 'Template for "stringType"',
+ <<<'FULL_MESSAGE'
+ - Two things are wrong
+ - Template for "stringType"
+ - Template for "uppercase"
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Two things are wrong',
+ 'stringType' => 'Template for "stringType"',
+ 'uppercase' => 'Template for "uppercase"',
+ ]
+));
diff --git a/tests/feature/Rules/AlnumTest.php b/tests/feature/Rules/AlnumTest.php
new file mode 100644
index 00000000..78663043
--- /dev/null
+++ b/tests/feature/Rules/AlnumTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::alnum()->assert('abc%1'),
+ '"abc%1" must contain only letters (a-z) and digits (0-9)',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::alnum(' ')->assert('abc%2'),
+ '"abc%2" must contain only letters (a-z), digits (0-9), and " "',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::alnum())->assert('abcd3'),
+ '"abcd3" must not contain letters (a-z) or digits (0-9)',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::alnum('% '))->assert('abc%4'),
+ '"abc%4" must not contain letters (a-z), digits (0-9), or "% "',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::alnum()->assert('abc^1'),
+ '- "abc^1" must contain only letters (a-z) and digits (0-9)',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::alnum())->assert('abcd2'),
+ '- "abcd2" must not contain letters (a-z) or digits (0-9)',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::alnum('* &%')->assert('abc^3'),
+ '- "abc^3" must contain only letters (a-z), digits (0-9), and "* &%"',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::alnum('^'))->assert('abc^4'),
+ '- "abc^4" must not contain letters (a-z), digits (0-9), or "^"',
+));
diff --git a/tests/feature/Rules/AlphaTest.php b/tests/feature/Rules/AlphaTest.php
new file mode 100644
index 00000000..c43c032e
--- /dev/null
+++ b/tests/feature/Rules/AlphaTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::alpha()->assert('aaa%a'),
+ '"aaa%a" must contain only letters (a-z)',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::alpha(' ')->assert('bbb%b'),
+ '"bbb%b" must contain only letters (a-z) and " "',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::alpha())->assert('ccccc'),
+ '"ccccc" must not contain letters (a-z)',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::alpha('% '))->assert('ddd%d'),
+ '"ddd%d" must not contain letters (a-z) or "% "',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::alpha()->assert('eee^e'),
+ '- "eee^e" must contain only letters (a-z)',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::alpha())->assert('fffff'),
+ '- "fffff" must not contain letters (a-z)',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::alpha('* &%')->assert('ggg^g'),
+ '- "ggg^g" must contain only letters (a-z) and "* &%"',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::alpha('^'))->assert('hhh^h'),
+ '- "hhh^h" must not contain letters (a-z) or "^"',
+));
diff --git a/tests/feature/Rules/AlwaysInvalidTest.php b/tests/feature/Rules/AlwaysInvalidTest.php
new file mode 100644
index 00000000..1e6c857a
--- /dev/null
+++ b/tests/feature/Rules/AlwaysInvalidTest.php
@@ -0,0 +1,18 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::alwaysInvalid()->assert('whatever'),
+ '"whatever" must be valid',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::alwaysInvalid()->assert(''),
+ '- "" must be valid',
+));
diff --git a/tests/feature/Rules/AlwaysValidTest.php b/tests/feature/Rules/AlwaysValidTest.php
new file mode 100644
index 00000000..a219bbb1
--- /dev/null
+++ b/tests/feature/Rules/AlwaysValidTest.php
@@ -0,0 +1,18 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::not(v::alwaysValid())->assert(true),
+ '`true` must be invalid',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::not(v::alwaysValid())->assert(true),
+ '- `true` must be invalid',
+));
diff --git a/tests/feature/Rules/ArrayTypeTest.php b/tests/feature/Rules/ArrayTypeTest.php
new file mode 100644
index 00000000..1ebe08e8
--- /dev/null
+++ b/tests/feature/Rules/ArrayTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::arrayType()->assert('teste'),
+ '"teste" must be an array',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::arrayType())->assert([]),
+ '`[]` must not be an array',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::arrayType()->assert(new ArrayObject()),
+ '- `ArrayObject { getArrayCopy() => [] }` must be an array',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::arrayType())->assert([1, 2, 3]),
+ '- `[1, 2, 3]` must not be an array',
+));
diff --git a/tests/feature/Rules/ArrayValTest.php b/tests/feature/Rules/ArrayValTest.php
new file mode 100644
index 00000000..ee365c93
--- /dev/null
+++ b/tests/feature/Rules/ArrayValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::arrayVal()->assert('Bla %123'),
+ '"Bla %123" must be an array value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::arrayVal())->assert([42]),
+ '`[42]` must not be an array value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::arrayVal()->assert(new stdClass()),
+ '- `stdClass {}` must be an array value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::arrayVal())->assert(new ArrayObject([2, 3])),
+ '- `ArrayObject { getArrayCopy() => [2, 3] }` must not be an array value',
+));
diff --git a/tests/feature/Rules/AttributesTest.php b/tests/feature/Rules/AttributesTest.php
new file mode 100644
index 00000000..c41edc80
--- /dev/null
+++ b/tests/feature/Rules/AttributesTest.php
@@ -0,0 +1,70 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Test\Stubs\WithAttributes;
+
+test('Default', expectAll(
+ fn() => v::attributes()->assert(new WithAttributes('', 'john.doe@gmail.com', '2024-06-23')),
+ 'name must not be empty',
+ '- name must not be empty',
+ ['name' => 'name must not be empty']
+));
+
+test('Inverted', expectAll(
+ fn() => v::attributes()->assert(new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', '+1234567890')),
+ 'phone must be a valid telephone number or must be null',
+ '- phone must be a valid telephone number or must be null',
+ ['phone' => 'phone must be a valid telephone number or must be null']
+));
+
+test('Not an object', expectAll(
+ fn() => v::attributes()->assert([]),
+ '`[]` must be an object',
+ '- `[]` must be an object',
+ ['attributes' => '`[]` must be an object']
+));
+
+test('Nullable', expectAll(
+ fn() => v::attributes()->assert(new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', 'not a phone number')),
+ 'phone must be a valid telephone number or must be null',
+ '- phone must be a valid telephone number or must be null',
+ ['phone' => 'phone must be a valid telephone number or must be null']
+));
+
+test('Multiple attributes, all failed', expectAll(
+ fn() => v::attributes()->assert(new WithAttributes('', 'not an email', 'not a date', 'not a phone number')),
+ 'name must not be empty',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }`
+ - name must not be empty
+ - email must be a valid email address
+ - All of the required rules must pass for birthdate
+ - birthdate must be a valid date in the format "2005-12-30"
+ - For comparison with now, birthdate must be a valid datetime
+ - phone must be a valid telephone number or must be null
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for `Respect\\Validation\\Test\\Stubs\\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }`',
+ 'name' => 'name must not be empty',
+ 'email' => 'email must be a valid email address',
+ 'birthdate' => [
+ '__root__' => 'All of the required rules must pass for birthdate',
+ 'date' => 'birthdate must be a valid date in the format "2005-12-30"',
+ 'dateTimeDiffLessThanOrEqual' => 'For comparison with now, birthdate must be a valid datetime',
+ ],
+ 'phone' => 'phone must be a valid telephone number or must be null',
+ ]
+));
+
+test('Multiple attributes, one failed', expectAll(
+ fn() => v::attributes()->assert(new WithAttributes('John Doe', 'john.doe@gmail.com', '22 years ago')),
+ 'birthdate must be a valid date in the format "2005-12-30"',
+ '- birthdate must be a valid date in the format "2005-12-30"',
+ ['birthdate' => 'birthdate must be a valid date in the format "2005-12-30"']
+));
diff --git a/tests/feature/Rules/Base64Test.php b/tests/feature/Rules/Base64Test.php
new file mode 100644
index 00000000..837477bb
--- /dev/null
+++ b/tests/feature/Rules/Base64Test.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::base64()->assert('=c3VyZS4'),
+ '"=c3VyZS4" must be a base64 encoded string',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::base64())->assert('c3VyZS4='),
+ '"c3VyZS4=" must not be a base64 encoded string',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::base64()->assert('=c3VyZS4'),
+ '- "=c3VyZS4" must be a base64 encoded string',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::base64())->assert('c3VyZS4='),
+ '- "c3VyZS4=" must not be a base64 encoded string',
+));
diff --git a/tests/feature/Rules/BaseTest.php b/tests/feature/Rules/BaseTest.php
new file mode 100644
index 00000000..5fc8f348
--- /dev/null
+++ b/tests/feature/Rules/BaseTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::base(61)->assert('Z01xSsg5675hic20dj'),
+ '"Z01xSsg5675hic20dj" must be a number in base 61',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::base(2)->assert(''),
+ '- "" must be a number in base 2',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::base(2))->assert('011010001'),
+ '"011010001" must not be a number in base 2',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::base(2))->assert('011010001'),
+ '- "011010001" must not be a number in base 2',
+));
diff --git a/tests/feature/Rules/BeetwenTest.php b/tests/feature/Rules/BeetwenTest.php
new file mode 100644
index 00000000..28e1b5bb
--- /dev/null
+++ b/tests/feature/Rules/BeetwenTest.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::between(1, 2)->assert(0),
+ '0 must be between 1 and 2',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::between('yesterday', 'tomorrow'))->assert('today'),
+ '"today" must not be between "yesterday" and "tomorrow"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::between('a', 'c')->assert('d'),
+ '- "d" must be between "a" and "c"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::between(-INF, INF))->assert(0),
+ '- 0 must not be between `-INF` and `INF`',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::not(v::between('a', 'b'))->assert('a'),
+ '- "a" must not be between "a" and "b"',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::between(1, 42))->assert(41),
+ '- 41 must not be between 1 and 42',
+));
diff --git a/tests/feature/Rules/BetweenExclusiveTest.php b/tests/feature/Rules/BetweenExclusiveTest.php
new file mode 100644
index 00000000..bd7d9a3f
--- /dev/null
+++ b/tests/feature/Rules/BetweenExclusiveTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::betweenExclusive(1, 10)->assert(12),
+ '12 must be greater than 1 and less than 10',
+ '- 12 must be greater than 1 and less than 10',
+ ['betweenExclusive' => '12 must be greater than 1 and less than 10']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::betweenExclusive(1, 10))->assert(5),
+ '5 must not be greater than 1 or less than 10',
+ '- 5 must not be greater than 1 or less than 10',
+ ['notBetweenExclusive' => '5 must not be greater than 1 or less than 10']
+));
+
+test('With template', expectAll(
+ fn() => v::betweenExclusive(1, 10)->setTemplate('Bewildered bees buzzed between blooming begonias')->assert(12),
+ 'Bewildered bees buzzed between blooming begonias',
+ '- Bewildered bees buzzed between blooming begonias',
+ ['betweenExclusive' => 'Bewildered bees buzzed between blooming begonias']
+));
+
+test('With name', expectAll(
+ fn() => v::betweenExclusive(1, 10)->setName('Range')->assert(10),
+ 'Range must be greater than 1 and less than 10',
+ '- Range must be greater than 1 and less than 10',
+ ['betweenExclusive' => 'Range must be greater than 1 and less than 10']
+));
diff --git a/tests/feature/Rules/BoolTypeTest.php b/tests/feature/Rules/BoolTypeTest.php
new file mode 100644
index 00000000..7a879eab
--- /dev/null
+++ b/tests/feature/Rules/BoolTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::boolType()->assert('teste'),
+ '"teste" must be a boolean',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::boolType())->assert(true),
+ '`true` must not be a boolean',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::boolType()->assert([]),
+ '- `[]` must be a boolean',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::boolType())->assert(false),
+ '- `false` must not be a boolean',
+));
diff --git a/tests/feature/Rules/BoolValTest.php b/tests/feature/Rules/BoolValTest.php
new file mode 100644
index 00000000..fa4b8a7c
--- /dev/null
+++ b/tests/feature/Rules/BoolValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::boolVal()->assert('ok'),
+ '"ok" must be a boolean value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::boolVal())->assert('yes'),
+ '"yes" must not be a boolean value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::boolVal()->assert('yep'),
+ '- "yep" must be a boolean value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::boolVal())->assert('on'),
+ '- "on" must not be a boolean value',
+));
diff --git a/tests/feature/Rules/BsnTest.php b/tests/feature/Rules/BsnTest.php
new file mode 100644
index 00000000..07472e66
--- /dev/null
+++ b/tests/feature/Rules/BsnTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::bsn()->assert('acb'),
+ '"acb" must be a valid BSN',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::bsn())->assert('612890053'),
+ '"612890053" must not be a valid BSN',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::bsn()->assert('abc'),
+ '- "abc" must be a valid BSN',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::bsn())->assert('612890053'),
+ '- "612890053" must not be a valid BSN',
+));
diff --git a/tests/feature/Rules/CallTest.php b/tests/feature/Rules/CallTest.php
new file mode 100644
index 00000000..6e394b5f
--- /dev/null
+++ b/tests/feature/Rules/CallTest.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::call('trim', v::noWhitespace())->assert(' two words '),
+ '"two words" must not contain whitespaces',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::call('stripslashes', v::stringType()))->assert(' some\thing '),
+ '" something " must not be a string',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::call('stripslashes', v::alwaysValid())->assert([]),
+ '`[]` must be a suitable argument for `stripslashes(string $string): string`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::call('strval', v::intType())->assert(1234),
+ '- "1234" must be an integer',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::not(v::call('is_float', v::boolType()))->assert(1.2),
+ '- `true` must not be a boolean',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::call('array_shift', v::alwaysValid())->assert(INF),
+ '- `INF` must be a suitable argument for `array_shift(array &$array): ?mixed`',
+));
diff --git a/tests/feature/Rules/CallableTypeTest.php b/tests/feature/Rules/CallableTypeTest.php
new file mode 100644
index 00000000..617a029a
--- /dev/null
+++ b/tests/feature/Rules/CallableTypeTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::callableType()->assert([]),
+ '`[]` must be a callable',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::callableType())->assert('trim'),
+ '`trim(string $string, string $characters = " \\n\\r\\t\\u000b\\u0000"): string` must not be a callable',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::callableType()->assert(true),
+ '- `true` must be a callable',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::callableType())->assert(function (): void {
+ // Do nothing
+ }),
+ '- `function (): void` must not be a callable',
+));
diff --git a/tests/feature/Rules/CallbackTest.php b/tests/feature/Rules/CallbackTest.php
new file mode 100644
index 00000000..36cb91f1
--- /dev/null
+++ b/tests/feature/Rules/CallbackTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::callback('is_string')->assert([]),
+ '`[]` must be valid',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::callback('is_string'))->assert('foo'),
+ '"foo" must be invalid',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::callback('is_string')->assert(true),
+ '- `true` must be valid',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::callback('is_string'))->assert('foo'),
+ '- "foo" must be invalid',
+));
diff --git a/tests/feature/Rules/CharsetTest.php b/tests/feature/Rules/CharsetTest.php
new file mode 100644
index 00000000..b3cbc9c4
--- /dev/null
+++ b/tests/feature/Rules/CharsetTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::charset('ASCII')->assert('açaí'),
+ '"açaí" must only contain characters from the `["ASCII"]` charset',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::charset('UTF-8'))->assert('açaí'),
+ '"açaí" must not contain any characters from the `["UTF-8"]` charset',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::charset('ASCII')->assert('açaí'),
+ '- "açaí" must only contain characters from the `["ASCII"]` charset',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::charset('UTF-8'))->assert('açaí'),
+ '- "açaí" must not contain any characters from the `["UTF-8"]` charset',
+));
diff --git a/tests/feature/Rules/CnhTest.php b/tests/feature/Rules/CnhTest.php
new file mode 100644
index 00000000..c231748f
--- /dev/null
+++ b/tests/feature/Rules/CnhTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::cnh()->assert('batman'),
+ '"batman" must be a valid CNH number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::cnh())->assert('02650306461'),
+ '"02650306461" must not be a valid CNH number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::cnh()->assert('bruce wayne'),
+ '- "bruce wayne" must be a valid CNH number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::cnh())->assert('02650306461'),
+ '- "02650306461" must not be a valid CNH number',
+));
diff --git a/tests/feature/Rules/CnpjTest.php b/tests/feature/Rules/CnpjTest.php
new file mode 100644
index 00000000..2a793bb2
--- /dev/null
+++ b/tests/feature/Rules/CnpjTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+require_once 'vendor/autoload.php';
+
+test('Scenario #1', expectMessage(
+ fn() => v::cnpj()->assert('não cnpj'),
+ '"não cnpj" must be a valid CNPJ number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::cnpj())->assert('65.150.175/0001-20'),
+ '"65.150.175/0001-20" must not be a valid CNPJ number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::cnpj()->assert('test'),
+ '- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be a valid CNPJ number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::cnpj())->assert('65.150.175/0001-20'),
+ '- "65.150.175/0001-20" must not be a valid CNPJ number',
+));
diff --git a/tests/feature/Rules/CntrlTest.php b/tests/feature/Rules/CntrlTest.php
new file mode 100644
index 00000000..f19b095a
--- /dev/null
+++ b/tests/feature/Rules/CntrlTest.php
@@ -0,0 +1,50 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+require_once 'vendor/autoload.php';
+
+test('Scenario #1', expectMessage(
+ fn() => v::control()->assert('16-50'),
+ '"16-50" must only contain control characters',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::control('16')->assert('16-50'),
+ '"16-50" must only contain control characters and "16"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::control())->assert("\n"),
+ '"\\n" must not contain control characters',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::control('16'))->assert("16\n"),
+ '"16\\n" must not contain control characters or "16"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::control()->assert('Foo'),
+ '- "Foo" must only contain control characters',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::control('Bar')->assert('Foo'),
+ '- "Foo" must only contain control characters and "Bar"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::control())->assert("\n"),
+ '- "\\n" must not contain control characters',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::control('Bar'))->assert("Bar\n"),
+ '- "Bar\\n" must not contain control characters or "Bar"',
+));
diff --git a/tests/feature/Rules/ConsecutiveTest.php b/tests/feature/Rules/ConsecutiveTest.php
new file mode 100644
index 00000000..4ca5cca7
--- /dev/null
+++ b/tests/feature/Rules/ConsecutiveTest.php
@@ -0,0 +1,92 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::trueVal())->assert(false),
+ '`false` must evaluate to `true`',
+ '- `false` must evaluate to `true`',
+ ['trueVal' => '`false` must evaluate to `true`']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::consecutive(v::alwaysValid(), v::trueVal()))->assert(true),
+ '`true` must not evaluate to `true`',
+ '- `true` must not evaluate to `true`',
+ ['notTrueVal' => '`true` must not evaluate to `true`']
+));
+
+test('Default with inverted failing rule', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::not(v::trueVal()))->assert(true),
+ '`true` must not evaluate to `true`',
+ '- `true` must not evaluate to `true`',
+ ['notTrueVal' => '`true` must not evaluate to `true`']
+));
+
+test('With wrapped name, default', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::trueVal()->setName('Wrapped'))->setName('Wrapper')->assert(false),
+ 'Wrapped must evaluate to `true`',
+ '- Wrapped must evaluate to `true`',
+ ['trueVal' => 'Wrapped must evaluate to `true`']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::trueVal())->setName('Wrapper')->assert(false),
+ 'Wrapper must evaluate to `true`',
+ '- Wrapper must evaluate to `true`',
+ ['trueVal' => 'Wrapper must evaluate to `true`']
+));
+
+test('With the name set in the wrapped rule of an inverted failing rule', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::not(v::trueVal()->setName('Wrapped'))->setName('Not'))->setName('Wrapper')->assert(true),
+ 'Wrapped must not evaluate to `true`',
+ '- Wrapped must not evaluate to `true`',
+ ['notTrueVal' => 'Wrapped must not evaluate to `true`']
+));
+
+test('With the name set in an inverted failing rule', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::not(v::trueVal())->setName('Not'))->setName('Wrapper')->assert(true),
+ 'Not must not evaluate to `true`',
+ '- Not must not evaluate to `true`',
+ ['notTrueVal' => 'Not must not evaluate to `true`']
+));
+
+test('With the name set in the "consecutive" that has an inverted failing rule', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::not(v::trueVal()))->setName('Wrapper')->assert(true),
+ 'Wrapper must not evaluate to `true`',
+ '- Wrapper must not evaluate to `true`',
+ ['notTrueVal' => 'Wrapper must not evaluate to `true`']
+));
+
+test('With template', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::trueVal())
+ ->setTemplate('Consecutive cool cats cunningly continuous cookies')
+ ->assert(false),
+ 'Consecutive cool cats cunningly continuous cookies',
+ '- Consecutive cool cats cunningly continuous cookies',
+ ['trueVal' => 'Consecutive cool cats cunningly continuous cookies']
+));
+
+test('With multiple templates', expectAll(
+ fn() => v::consecutive(v::alwaysValid(), v::trueVal())
+ ->setTemplates(['trueVal' => 'Clever clowns craft consecutive clever clocks'])
+ ->assert(false),
+ 'Clever clowns craft consecutive clever clocks',
+ '- Clever clowns craft consecutive clever clocks',
+ ['trueVal' => 'Clever clowns craft consecutive clever clocks']
+));
+
+test('Real example', expectAll(
+ fn() => v::consecutive(
+ v::key('countyCode', v::countryCode()),
+ v::lazy(fn($input) => v::key('subdivisionCode', v::subdivisionCode($input['countyCode'])))
+ )->assert(['countyCode' => 'BR', 'subdivisionCode' => 'CA']),
+ 'subdivisionCode must be a subdivision code of Brazil',
+ '- subdivisionCode must be a subdivision code of Brazil',
+ ['subdivisionCode' => 'subdivisionCode must be a subdivision code of Brazil']
+));
diff --git a/tests/feature/Rules/ConsonantTest.php b/tests/feature/Rules/ConsonantTest.php
new file mode 100644
index 00000000..feefb31a
--- /dev/null
+++ b/tests/feature/Rules/ConsonantTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::consonant()->assert('aeiou'),
+ '"aeiou" must only contain consonants',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::consonant('d')->assert('daeiou'),
+ '"daeiou" must only contain consonants and "d"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::consonant())->assert('bcd'),
+ '"bcd" must not contain consonants',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::consonant('a'))->assert('abcd'),
+ '"abcd" must not contain consonants or "a"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::consonant()->assert('aeiou'),
+ '- "aeiou" must only contain consonants',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::consonant('d')->assert('daeiou'),
+ '- "daeiou" must only contain consonants and "d"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::consonant())->assert('bcd'),
+ '- "bcd" must not contain consonants',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::consonant('a'))->assert('abcd'),
+ '- "abcd" must not contain consonants or "a"',
+));
diff --git a/tests/feature/Rules/ContainsAnyTest.php b/tests/feature/Rules/ContainsAnyTest.php
new file mode 100644
index 00000000..2ba491f7
--- /dev/null
+++ b/tests/feature/Rules/ContainsAnyTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::containsAny(['foo', 'bar'])->assert('baz'),
+ '"baz" must contain at least one value from `["foo", "bar"]`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::containsAny(['foo', 'bar']))->assert('fool'),
+ '"fool" must not contain any value from `["foo", "bar"]`',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::containsAny(['foo', 'bar'])->assert(['baz']),
+ '- `["baz"]` must contain at least one value from `["foo", "bar"]`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::containsAny(['foo', 'bar'], true))->assert(['bar', 'foo']),
+ '- `["bar", "foo"]` must not contain any value from `["foo", "bar"]`',
+));
diff --git a/tests/feature/Rules/ContainsTest.php b/tests/feature/Rules/ContainsTest.php
new file mode 100644
index 00000000..0f523ae9
--- /dev/null
+++ b/tests/feature/Rules/ContainsTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::contains('foo')->assert('bar'),
+ '"bar" must contain "foo"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::contains('foo'))->assert('fool'),
+ '"fool" must not contain "foo"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::contains('foo')->assert(['bar']),
+ '- `["bar"]` must contain "foo"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::contains('foo', true))->assert(['bar', 'foo']),
+ '- `["bar", "foo"]` must not contain "foo"',
+));
diff --git a/tests/feature/Rules/CountableTest.php b/tests/feature/Rules/CountableTest.php
new file mode 100644
index 00000000..fb157b19
--- /dev/null
+++ b/tests/feature/Rules/CountableTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::countable()->assert(1.0),
+ '1.0 must be a countable value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::countable())->assert([]),
+ '`[]` must not be a countable value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::countable()->assert('Not countable!'),
+ '- "Not countable!" must be a countable value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::countable())->assert(new ArrayObject()),
+ '- `ArrayObject { getArrayCopy() => [] }` must not be a countable value',
+));
diff --git a/tests/feature/Rules/CountryCodeTest.php b/tests/feature/Rules/CountryCodeTest.php
new file mode 100644
index 00000000..5d8ee3e6
--- /dev/null
+++ b/tests/feature/Rules/CountryCodeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::countryCode()->assert('1'),
+ '"1" must be a valid country code',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::countryCode())->assert('BR'),
+ '"BR" must not be a valid country code',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::countryCode()->assert('1'),
+ '- "1" must be a valid country code',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::countryCode())->assert('BR'),
+ '- "BR" must not be a valid country code',
+));
diff --git a/tests/feature/Rules/CpfTest.php b/tests/feature/Rules/CpfTest.php
new file mode 100644
index 00000000..ff862918
--- /dev/null
+++ b/tests/feature/Rules/CpfTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::cpf()->assert('this thing'),
+ '"this thing" must be a valid CPF number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::cpf())->assert('276.865.775-11'),
+ '"276.865.775-11" must not be a valid CPF number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::cpf()->assert('your mother'),
+ '- "your mother" must be a valid CPF number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::cpf())->assert('61836182848'),
+ '- "61836182848" must not be a valid CPF number',
+));
diff --git a/tests/feature/Rules/CreditCardTest.php b/tests/feature/Rules/CreditCardTest.php
new file mode 100644
index 00000000..e2d411c4
--- /dev/null
+++ b/tests/feature/Rules/CreditCardTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::creditCard('Discover')->assert(3566002020360505),
+ '3566002020360505 must be a valid Discover credit card number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::creditCard('Visa'))->assert(4024007153361885),
+ '4024007153361885 must not be a valid Visa credit card number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::creditCard('MasterCard')->assert(3566002020360505),
+ '- 3566002020360505 must be a valid MasterCard credit card number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::creditCard())->assert(5555444433331111),
+ '- 5555444433331111 must not be a valid credit card number',
+));
diff --git a/tests/feature/Rules/CurrencyCodeTest.php b/tests/feature/Rules/CurrencyCodeTest.php
new file mode 100644
index 00000000..8b3baa0b
--- /dev/null
+++ b/tests/feature/Rules/CurrencyCodeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::currencyCode()->assert('batman'),
+ '"batman" must be a valid currency code',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::currencyCode())->assert('BRL'),
+ '"BRL" must not be a valid currency code',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::currencyCode()->assert('ppz'),
+ '- "ppz" must be a valid currency code',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::currencyCode())->assert('GBP'),
+ '- "GBP" must not be a valid currency code',
+));
diff --git a/tests/feature/Rules/DateTest.php b/tests/feature/Rules/DateTest.php
new file mode 100644
index 00000000..a1fd72fd
--- /dev/null
+++ b/tests/feature/Rules/DateTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+test('Scenario #1', expectMessage(
+ fn() => v::date()->assert('2018-01-29T08:32:54+00:00'),
+ '"2018-01-29T08:32:54+00:00" must be a valid date in the format "2005-12-30"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::date())->assert('2018-01-29'),
+ '"2018-01-29" must not be a valid date in the format "2005-12-30"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::date()->assert('2018-01-29T08:32:54+00:00'),
+ '- "2018-01-29T08:32:54+00:00" must be a valid date in the format "2005-12-30"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::date('d/m/Y'))->assert('29/01/2018'),
+ '- "29/01/2018" must not be a valid date in the format "30/12/2005"',
+));
diff --git a/tests/feature/Rules/DateTimeDiffTest.php b/tests/feature/Rules/DateTimeDiffTest.php
new file mode 100644
index 00000000..6b79859c
--- /dev/null
+++ b/tests/feature/Rules/DateTimeDiffTest.php
@@ -0,0 +1,148 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('With $type = "years"', expectAll(
+ fn() => v::dateTimeDiff('years', v::equals(2))->assert('1 year ago'),
+ 'The number of years between now and 1 year ago must be equal to 2',
+ '- The number of years between now and 1 year ago must be equal to 2',
+ ['dateTimeDiffEquals' => 'The number of years between now and 1 year ago must be equal to 2']
+));
+
+test('With $type = "months"', expectAll(
+ fn() => v::dateTimeDiff('months', v::equals(3))->assert('2 months ago'),
+ 'The number of months between now and 2 months ago must be equal to 3',
+ '- The number of months between now and 2 months ago must be equal to 3',
+ ['dateTimeDiffEquals' => 'The number of months between now and 2 months ago must be equal to 3']
+));
+
+test('With $type = "days"', expectAll(
+ fn() => v::dateTimeDiff('days', v::equals(4))->assert('3 days ago'),
+ 'The number of days between now and 3 days ago must be equal to 4',
+ '- The number of days between now and 3 days ago must be equal to 4',
+ ['dateTimeDiffEquals' => 'The number of days between now and 3 days ago must be equal to 4']
+));
+
+test('With $type = "hours"', expectAll(
+ fn() => v::dateTimeDiff('hours', v::equals(5))->assert('4 hours ago'),
+ 'The number of hours between now and 4 hours ago must be equal to 5',
+ '- The number of hours between now and 4 hours ago must be equal to 5',
+ ['dateTimeDiffEquals' => 'The number of hours between now and 4 hours ago must be equal to 5']
+));
+
+test('With $type = "minutes"', expectAll(
+ fn() => v::dateTimeDiff('minutes', v::equals(6))->assert('5 minutes ago'),
+ 'The number of minutes between now and 5 minutes ago must be equal to 6',
+ '- The number of minutes between now and 5 minutes ago must be equal to 6',
+ ['dateTimeDiffEquals' => 'The number of minutes between now and 5 minutes ago must be equal to 6']
+));
+
+test('With $type = "microseconds"', expectAll(
+ fn() => v::dateTimeDiff('microseconds', v::equals(7))->assert('6 microseconds ago'),
+ 'The number of microseconds between now and 6 microseconds ago must be equal to 7',
+ '- The number of microseconds between now and 6 microseconds ago must be equal to 7',
+ ['dateTimeDiffEquals' => 'The number of microseconds between now and 6 microseconds ago must be equal to 7']
+));
+
+test('With custom $format', expectAllToMatch(
+ fn() => v::dateTimeDiff('years', v::lessThan(8), 'd/m/Y')->assert('09/12/1988'),
+ 'The number of years between %d/%d/%d and 09/12/1988 must be less than 8',
+ '- The number of years between %d/%d/%d and 09/12/1988 must be less than 8',
+ ['dateTimeDiffLessThan' => 'The number of years between %d/%d/%d and 09/12/1988 must be less than 8']
+));
+
+test('With input in non-parseable date', expectAll(
+ fn() => v::dateTimeDiff('years', v::equals(2))->assert('not a date'),
+ 'For comparison with now, "not a date" must be a valid datetime',
+ '- For comparison with now, "not a date" must be a valid datetime',
+ ['dateTimeDiffEquals' => 'For comparison with now, "not a date" must be a valid datetime']
+));
+
+test('With input in incorrect $format', expectAllToMatch(
+ fn() => v::dateTimeDiff('years', v::equals(2), 'Y-m-d')->assert('1 year ago'),
+ 'For comparison with %d-%d-%d, "1 year ago" must be a valid datetime in the format %d-%d-%d',
+ '- For comparison with %d-%d-%d, "1 year ago" must be a valid datetime in the format %d-%d-%d',
+ ['dateTimeDiffEquals' => 'For comparison with %d-%d-%d, "1 year ago" must be a valid datetime in the format %d-%d-%d']
+));
+
+test('With custom $now', expectAllToMatch(
+ fn() => v::dateTimeDiff('years', v::lessThan(9), null, new DateTimeImmutable())->assert('09/12/1988'),
+ 'The number of years between %d-%d-%d %d:%d:%d.%d and 09/12/1988 must be less than 9',
+ '- The number of years between %d-%d-%d %d:%d:%d.%d and 09/12/1988 must be less than 9',
+ ['dateTimeDiffLessThan' => 'The number of years between %d-%d-%d %d:%d:%d.%d and 09/12/1988 must be less than 9']
+));
+
+test('With custom template', expectAll(
+ fn() => v::dateTimeDiff('years', v::equals(2)->setTemplate('Custom template'))->assert('1 year ago'),
+ 'Custom template',
+ '- Custom template',
+ ['equals' => 'Custom template']
+));
+
+test('Wrapped by "not"', expectAll(
+ fn() => v::not(v::dateTimeDiff('years', v::lessThan(8)))->assert('7 year ago'),
+ 'The number of years between now and 7 year ago must not be less than 8',
+ '- The number of years between now and 7 year ago must not be less than 8',
+ ['notDateTimeDiffLessThan' => 'The number of years between now and 7 year ago must not be less than 8']
+));
+
+test('Wrapping "not"', expectAll(
+ fn() => v::dateTimeDiff('years', v::not(v::lessThan(9)))->assert('8 year ago'),
+ 'The number of years between now and 8 year ago must not be less than 9',
+ '- The number of years between now and 8 year ago must not be less than 9',
+ ['dateTimeDiffNotLessThan' => 'The number of years between now and 8 year ago must not be less than 9']
+));
+
+test('Wrapped with custom template', expectAll(
+ fn() => v::dateTimeDiff('years', v::equals(2)->setTemplate('Wrapped with custom template'))->assert('1 year ago'),
+ 'Wrapped with custom template',
+ '- Wrapped with custom template',
+ ['equals' => 'Wrapped with custom template']
+));
+
+test('Wrapper with custom template', expectAll(
+ fn() => v::dateTimeDiff('years', v::equals(2))->setTemplate('Wrapper with custom template')->assert('1 year ago'),
+ 'Wrapper with custom template',
+ '- Wrapper with custom template',
+ ['dateTimeDiffEquals' => 'Wrapper with custom template']
+));
+
+test('Without subsequent result', expectAll(
+ fn() => v::dateTimeDiff('years', v::primeNumber()->between(2, 5))->assert('1 year ago'),
+ 'The number of years between now and 1 year ago must be a prime number',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1 year ago
+ - The number of years between now and 1 year ago must be a prime number
+ - The number of years between now and 1 year ago must be between 2 and 5
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1 year ago',
+ 'dateTimeDiffPrimeNumber' => 'The number of years between now and 1 year ago must be a prime number',
+ 'dateTimeDiffBetween' => 'The number of years between now and 1 year ago must be between 2 and 5',
+ ]
+));
+
+test('Without subsequent result with templates', expectAll(
+ fn() => v::dateTimeDiff('years', v::primeNumber()->between(2, 5))->setTemplates([
+ 'dateTimeDiff' => [
+ 'primeNumber' => 'Interval must be a valid prime number',
+ 'between' => 'Interval must be between 2 and 5',
+ ],
+ ])->assert('1 year ago'),
+ 'The number of years between now and 1 year ago must be a prime number',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1 year ago
+ - The number of years between now and 1 year ago must be a prime number
+ - The number of years between now and 1 year ago must be between 2 and 5
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1 year ago',
+ 'dateTimeDiffPrimeNumber' => 'The number of years between now and 1 year ago must be a prime number',
+ 'dateTimeDiffBetween' => 'The number of years between now and 1 year ago must be between 2 and 5',
+ ]
+));
diff --git a/tests/feature/Rules/DateTimeTest.php b/tests/feature/Rules/DateTimeTest.php
new file mode 100644
index 00000000..8d8cfe03
--- /dev/null
+++ b/tests/feature/Rules/DateTimeTest.php
@@ -0,0 +1,50 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+test('Scenario #1', expectMessage(
+ fn() => v::dateTime()->assert('FooBarBazz'),
+ '"FooBarBazz" must be a valid date/time',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::dateTime('c')->assert('06-12-1995'),
+ '"06-12-1995" must be a valid date/time in the format "2005-12-30T01:02:03+00:00"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::dateTime()->assert('QuxQuuxx'),
+ '- "QuxQuuxx" must be a valid date/time',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::dateTime('r')->assert(2018013030),
+ '- 2018013030 must be a valid date/time in the format "Fri, 30 Dec 2005 01:02:03 +0000"',
+));
+
+test('Scenario #5', expectMessage(
+ fn() => v::not(v::dateTime())->assert('4 days ago'),
+ '"4 days ago" must not be a valid date/time',
+));
+
+test('Scenario #6', expectMessage(
+ fn() => v::not(v::dateTime('Y-m-d'))->assert('1988-09-09'),
+ '"1988-09-09" must not be a valid date/time in the format "2005-12-30"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::dateTime())->assert('+3 weeks'),
+ '- "+3 weeks" must not be a valid date/time',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::dateTime('d/m/y'))->assert('23/07/99'),
+ '- "23/07/99" must not be a valid date/time in the format "30/12/05"',
+));
diff --git a/tests/feature/Rules/DecimalTest.php b/tests/feature/Rules/DecimalTest.php
new file mode 100644
index 00000000..11a2936e
--- /dev/null
+++ b/tests/feature/Rules/DecimalTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::decimal(3)->assert(0.1234),
+ '0.1234 must have 3 decimals',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::decimal(2)->assert(0.123),
+ '- 0.123 must have 2 decimals',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::decimal(5))->assert(0.12345),
+ '0.12345 must not have 5 decimals',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::decimal(2))->assert(0.34),
+ '- 0.34 must not have 2 decimals',
+));
diff --git a/tests/feature/Rules/DigitTest.php b/tests/feature/Rules/DigitTest.php
new file mode 100644
index 00000000..21b82b16
--- /dev/null
+++ b/tests/feature/Rules/DigitTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::digit()->assert('abc'),
+ '"abc" must contain only digits (0-9)',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::digit('-')->assert('a-b'),
+ '"a-b" must contain only digits (0-9) and "-"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::digit())->assert('123'),
+ '"123" must not contain digits (0-9)',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::digit('-'))->assert('1-3'),
+ '"1-3" must not contain digits (0-9) and "-"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::digit()->assert('abc'),
+ '- "abc" must contain only digits (0-9)',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::digit('-')->assert('a-b'),
+ '- "a-b" must contain only digits (0-9) and "-"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::digit())->assert('123'),
+ '- "123" must not contain digits (0-9)',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::digit('-'))->assert('1-3'),
+ '- "1-3" must not contain digits (0-9) and "-"',
+));
diff --git a/tests/feature/Rules/DirectoryTest.php b/tests/feature/Rules/DirectoryTest.php
new file mode 100644
index 00000000..d6ab664e
--- /dev/null
+++ b/tests/feature/Rules/DirectoryTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::directory()->assert('batman'),
+ '"batman" must be a directory',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::directory())->assert(dirname('/etc/')),
+ '"/" must not be a directory',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::directory()->assert('ppz'),
+ '- "ppz" must be a directory',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::directory())->assert(dirname('/etc/')),
+ '- "/" must not be a directory',
+));
diff --git a/tests/feature/Rules/DomainTest.php b/tests/feature/Rules/DomainTest.php
new file mode 100644
index 00000000..e22ba5fe
--- /dev/null
+++ b/tests/feature/Rules/DomainTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::domain()->assert('batman'),
+ '"batman" must be a valid domain',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::domain())->assert('r--w.com'),
+ '"r--w.com" must not be a valid domain',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::domain()->assert('p-éz-.kk'),
+ '- "p-éz-.kk" must be a valid domain',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::domain())->assert('github.com'),
+ '- "github.com" must not be a valid domain',
+));
diff --git a/tests/feature/Rules/EachTest.php b/tests/feature/Rules/EachTest.php
new file mode 100644
index 00000000..b2f19a2a
--- /dev/null
+++ b/tests/feature/Rules/EachTest.php
@@ -0,0 +1,241 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Non-iterable', expectAll(
+ fn() => v::each(v::intType())->assert(null),
+ '`null` must be iterable',
+ '- `null` must be iterable',
+ ['each' => '`null` must be iterable']
+));
+
+test('Empty', expectAll(
+ fn() => v::each(v::intType())->assert([]),
+ 'The value must not be empty',
+ '- The value must not be empty',
+ ['each' => 'The value must not be empty']
+));
+
+test('Default', expectAll(
+ fn() => v::each(v::intType())->assert(['a', 'b', 'c']),
+ '"a" must be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in `["a", "b", "c"]` must be valid
+ - "a" must be an integer
+ - "b" must be an integer
+ - "c" must be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in `["a", "b", "c"]` must be valid',
+ 'intType.1' => '"a" must be an integer',
+ 'intType.2' => '"b" must be an integer',
+ 'intType.3' => '"c" must be an integer',
+ ]
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::each(v::intType()))->assert([1, 2, 3]),
+ '1 must not be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in `[1, 2, 3]` must be invalid
+ - 1 must not be an integer
+ - 2 must not be an integer
+ - 3 must not be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in `[1, 2, 3]` must be invalid',
+ 'intType.1' => '1 must not be an integer',
+ 'intType.2' => '2 must not be an integer',
+ 'intType.3' => '3 must not be an integer',
+ ]
+));
+
+test('With name, non-iterable', expectAll(
+ fn() => v::each(v::intType()->setName('Wrapped'))->setName('Wrapper')->assert(null),
+ 'Wrapped must be iterable',
+ '- Wrapped must be iterable',
+ ['Wrapped' => 'Wrapped must be iterable']
+));
+
+test('With name, empty', expectAll(
+ fn() => v::each(v::intType()->setName('Wrapped'))->setName('Wrapper')->assert([]),
+ 'Wrapped must not be empty',
+ '- Wrapped must not be empty',
+ ['Wrapped' => 'Wrapped must not be empty']
+));
+
+test('With name, default', expectAll(
+ fn() => v::each(v::intType()->setName('Wrapped'))->setName('Wrapper')->assert(['a', 'b', 'c']),
+ 'Wrapped must be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in Wrapped must be valid
+ - Wrapped must be an integer
+ - Wrapped must be an integer
+ - Wrapped must be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in Wrapped must be valid',
+ 'intType.1' => 'Wrapped must be an integer',
+ 'intType.2' => 'Wrapped must be an integer',
+ 'intType.3' => 'Wrapped must be an integer',
+ ]
+));
+
+test('With name, inverted', expectAll(
+ fn() => v::not(v::each(v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not')->assert([1, 2, 3]),
+ 'Wrapped must not be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in Wrapped must be invalid
+ - Wrapped must not be an integer
+ - Wrapped must not be an integer
+ - Wrapped must not be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in Wrapped must be invalid',
+ 'intType.1' => 'Wrapped must not be an integer',
+ 'intType.2' => 'Wrapped must not be an integer',
+ 'intType.3' => 'Wrapped must not be an integer',
+ ]
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::each(v::intType())->setName('Wrapper')->assert(['a', 'b', 'c']),
+ 'Wrapper must be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in Wrapper must be valid
+ - Wrapper must be an integer
+ - Wrapper must be an integer
+ - Wrapper must be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in Wrapper must be valid',
+ 'intType.1' => 'Wrapper must be an integer',
+ 'intType.2' => 'Wrapper must be an integer',
+ 'intType.3' => 'Wrapper must be an integer',
+ ]
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::each(v::intType())->setName('Wrapper'))->setName('Not')->assert([1, 2, 3]),
+ 'Wrapper must not be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in Wrapper must be invalid
+ - Wrapper must not be an integer
+ - Wrapper must not be an integer
+ - Wrapper must not be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in Wrapper must be invalid',
+ 'intType.1' => 'Wrapper must not be an integer',
+ 'intType.2' => 'Wrapper must not be an integer',
+ 'intType.3' => 'Wrapper must not be an integer',
+ ]
+));
+
+test('With Not name, inverted', expectAll(
+ fn() => v::not(v::each(v::intType()))->setName('Not')->assert([1, 2, 3]),
+ 'Not must not be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in Not must be invalid
+ - Not must not be an integer
+ - Not must not be an integer
+ - Not must not be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in Not must be invalid',
+ 'intType.1' => 'Not must not be an integer',
+ 'intType.2' => 'Not must not be an integer',
+ 'intType.3' => 'Not must not be an integer',
+ ]
+));
+
+test('With template, non-iterable', expectAll(
+ fn() => v::each(v::intType())->setTemplate('You should have passed an iterable')->assert(null),
+ 'You should have passed an iterable',
+ '- You should have passed an iterable',
+ ['each' => 'You should have passed an iterable']
+));
+
+test('With template, empty', expectAll(
+ fn() => v::each(v::intType())->setTemplate('You should have passed an non-empty')
+ ->assert([]),
+ 'You should have passed an non-empty',
+ '- You should have passed an non-empty',
+ ['each' => 'You should have passed an non-empty']
+));
+
+test('With template, default', expectAll(
+ fn() => v::each(v::intType())
+ ->setTemplate('All items should have been integers')
+ ->assert(['a', 'b', 'c']),
+ 'All items should have been integers',
+ '- All items should have been integers',
+ ['each' => 'All items should have been integers']
+));
+
+test('with template, inverted', expectAll(
+ fn() => v::not(v::each(v::intType()))
+ ->setTemplate('All items should not have been integers')
+ ->assert([1, 2, 3]),
+ 'All items should not have been integers',
+ '- All items should not have been integers',
+ ['notEach' => 'All items should not have been integers']
+));
+
+test('With array template, default', expectAll(
+ fn() => v::each(v::intType())
+ ->setTemplates([
+ 'each' => [
+ '__root__' => 'Here a sequence of items that did not pass the validation',
+ 'intType.1' => 'First item should have been an integer',
+ 'intType.2' => 'Second item should have been an integer',
+ 'intType.3' => 'Third item should have been an integer',
+ ],
+ ])
+ ->assert(['a', 'b', 'c']),
+ 'First item should have been an integer',
+ <<<'FULL_MESSAGE'
+ - Here a sequence of items that did not pass the validation
+ - First item should have been an integer
+ - Second item should have been an integer
+ - Third item should have been an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Here a sequence of items that did not pass the validation',
+ 'intType.1' => 'First item should have been an integer',
+ 'intType.2' => 'Second item should have been an integer',
+ 'intType.3' => 'Third item should have been an integer',
+ ]
+));
+
+test('With array template and name, default', expectAll(
+ fn() => v::each(v::intType()->setName('Wrapped'))
+ ->setName('Wrapper')
+ ->setTemplates([
+ 'Wrapped' => [
+ '__root__' => 'Here a sequence of items that did not pass the validation',
+ 'Wrapped.1' => 'First item should have been an integer',
+ 'Wrapped.2' => 'Second item should have been an integer',
+ 'Wrapped.3' => 'Third item should have been an integer',
+ ],
+ ])
+ ->assert(['a', 'b', 'c']),
+ 'Wrapped must be an integer',
+ <<<'FULL_MESSAGE'
+ - Each item in Wrapped must be valid
+ - Wrapped must be an integer
+ - Wrapped must be an integer
+ - Wrapped must be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => 'Each item in Wrapped must be valid',
+ 'intType.1' => 'Wrapped must be an integer',
+ 'intType.2' => 'Wrapped must be an integer',
+ 'intType.3' => 'Wrapped must be an integer',
+ ]
+));
diff --git a/tests/feature/Rules/EmailTest.php b/tests/feature/Rules/EmailTest.php
new file mode 100644
index 00000000..d10e52e1
--- /dev/null
+++ b/tests/feature/Rules/EmailTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::email()->assert('batman'),
+ '"batman" must be a valid email address',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::email())->assert('bruce.wayne@gothancity.com'),
+ '"bruce.wayne@gothancity.com" must not be an email address',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::email()->assert('bruce wayne'),
+ '- "bruce wayne" must be a valid email address',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::email())->assert('iambatman@gothancity.com'),
+ '- "iambatman@gothancity.com" must not be an email address',
+));
diff --git a/tests/feature/Rules/EndsWithTest.php b/tests/feature/Rules/EndsWithTest.php
new file mode 100644
index 00000000..9302b089
--- /dev/null
+++ b/tests/feature/Rules/EndsWithTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::endsWith('foo')->assert('bar'),
+ '"bar" must end with "foo"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::endsWith('foo'))->assert(['bar', 'foo']),
+ '`["bar", "foo"]` must not end with "foo"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::endsWith('foo')->assert(''),
+ '- "" must end with "foo"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::endsWith('foo'))->assert(['bar', 'foo']),
+ '- `["bar", "foo"]` must not end with "foo"',
+));
diff --git a/tests/feature/Rules/EqualsTest.php b/tests/feature/Rules/EqualsTest.php
new file mode 100644
index 00000000..1c4baf2e
--- /dev/null
+++ b/tests/feature/Rules/EqualsTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::equals(123)->assert(321),
+ '321 must be equal to 123',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::equals(321))->assert(321),
+ '321 must not be equal to 321',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::equals(123)->assert(321),
+ '- 321 must be equal to 123',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::equals(321))->assert(321),
+ '- 321 must not be equal to 321',
+));
diff --git a/tests/feature/Rules/EquivalentTest.php b/tests/feature/Rules/EquivalentTest.php
new file mode 100644
index 00000000..13e5f606
--- /dev/null
+++ b/tests/feature/Rules/EquivalentTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::equivalent(true)->assert(false),
+ '`false` must be equivalent to `true`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::equivalent('Something'))->assert('someThing'),
+ '"someThing" must not be equivalent to "Something"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::equivalent(123)->assert('true'),
+ '- "true" must be equivalent to 123',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::equivalent(true))->assert(1),
+ '- 1 must not be equivalent to `true`',
+));
diff --git a/tests/feature/Rules/EvenTest.php b/tests/feature/Rules/EvenTest.php
new file mode 100644
index 00000000..9cc551a6
--- /dev/null
+++ b/tests/feature/Rules/EvenTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::even()->assert(-1),
+ '-1 must be an even number',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::even()->assert(5),
+ '- 5 must be an even number',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::even())->assert(6),
+ '6 must be an odd number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::even())->assert(8),
+ '- 8 must be an odd number',
+));
diff --git a/tests/feature/Rules/ExecutableTest.php b/tests/feature/Rules/ExecutableTest.php
new file mode 100644
index 00000000..919a2dd0
--- /dev/null
+++ b/tests/feature/Rules/ExecutableTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::executable()->assert('bar'),
+ '"bar" must be an executable file',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::executable())->assert('tests/fixtures/executable'),
+ '"tests/fixtures/executable" must not be an executable file',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::executable()->assert('bar'),
+ '- "bar" must be an executable file',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::executable())->assert('tests/fixtures/executable'),
+ '- "tests/fixtures/executable" must not be an executable file',
+));
diff --git a/tests/feature/Rules/ExistsTest.php b/tests/feature/Rules/ExistsTest.php
new file mode 100644
index 00000000..d0e02980
--- /dev/null
+++ b/tests/feature/Rules/ExistsTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::exists()->assert('/path/of/a/non-existent/file'),
+ '"/path/of/a/non-existent/file" must be an existing file',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::exists())->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must not be an existing file',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::exists()->assert('/path/of/a/non-existent/file'),
+ '- "/path/of/a/non-existent/file" must be an existing file',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::exists())->assert('tests/fixtures/valid-image.png'),
+ '- "tests/fixtures/valid-image.png" must not be an existing file',
+));
diff --git a/tests/feature/Rules/ExtensionTest.php b/tests/feature/Rules/ExtensionTest.php
new file mode 100644
index 00000000..46c8e1d9
--- /dev/null
+++ b/tests/feature/Rules/ExtensionTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::extension('png')->assert('filename.txt'),
+ '"filename.txt" must have "png" extension',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::extension('gif'))->assert('filename.gif'),
+ '"filename.gif" must not have "gif" extension',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::extension('mp3')->assert('filename.wav'),
+ '- "filename.wav" must have "mp3" extension',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::extension('png'))->assert('tests/fixtures/invalid-image.png'),
+ '- "tests/fixtures/invalid-image.png" must not have "png" extension',
+));
diff --git a/tests/feature/Rules/FactorTest.php b/tests/feature/Rules/FactorTest.php
new file mode 100644
index 00000000..3638c396
--- /dev/null
+++ b/tests/feature/Rules/FactorTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::factor(3)->assert(2),
+ '2 must be a factor of 3',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::factor(0))->assert(300),
+ '300 must not be a factor of 0',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::factor(5)->assert(3),
+ '- 3 must be a factor of 5',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::factor(6))->assert(1),
+ '- 1 must not be a factor of 6',
+));
diff --git a/tests/feature/Rules/FalseValTest.php b/tests/feature/Rules/FalseValTest.php
new file mode 100644
index 00000000..164e0536
--- /dev/null
+++ b/tests/feature/Rules/FalseValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::falseVal()->assert(true),
+ '`true` must evaluate to `false`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::falseVal())->assert('false'),
+ '"false" must not evaluate to `false`',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::falseVal()->assert(1),
+ '- 1 must evaluate to `false`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::falseVal())->assert(0),
+ '- 0 must not evaluate to `false`',
+));
diff --git a/tests/feature/Rules/FibonacciTest.php b/tests/feature/Rules/FibonacciTest.php
new file mode 100644
index 00000000..e717946b
--- /dev/null
+++ b/tests/feature/Rules/FibonacciTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::fibonacci()->assert(4),
+ '4 must be a valid Fibonacci number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::fibonacci())->assert(5),
+ '5 must not be a valid Fibonacci number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::fibonacci()->assert(16),
+ '- 16 must be a valid Fibonacci number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::fibonacci())->assert(21),
+ '- 21 must not be a valid Fibonacci number',
+));
diff --git a/tests/feature/Rules/FileTest.php b/tests/feature/Rules/FileTest.php
new file mode 100644
index 00000000..6c107fcf
--- /dev/null
+++ b/tests/feature/Rules/FileTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::file()->assert('tests/fixtures/non-existent.sh'),
+ '"tests/fixtures/non-existent.sh" must be a valid file',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::file())->assert('tests/fixtures/valid-image.png'),
+ '"tests/fixtures/valid-image.png" must be an invalid file',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::file()->assert('tests/fixtures/non-existent.sh'),
+ '- "tests/fixtures/non-existent.sh" must be a valid file',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::file())->assert('tests/fixtures/valid-image.png'),
+ '- "tests/fixtures/valid-image.png" must be an invalid file',
+));
diff --git a/tests/feature/Rules/FilterVarTest.php b/tests/feature/Rules/FilterVarTest.php
new file mode 100644
index 00000000..7c0c98a7
--- /dev/null
+++ b/tests/feature/Rules/FilterVarTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::filterVar(FILTER_VALIDATE_IP)->assert(42),
+ '42 must be valid',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::filterVar(FILTER_VALIDATE_BOOLEAN))->assert('On'),
+ '"On" must not be valid',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::filterVar(FILTER_VALIDATE_EMAIL)->assert(1.5),
+ '- 1.5 must be valid',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::filterVar(FILTER_VALIDATE_FLOAT))->assert(1.0),
+ '- 1.0 must not be valid',
+));
diff --git a/tests/feature/Rules/FiniteTest.php b/tests/feature/Rules/FiniteTest.php
new file mode 100644
index 00000000..b8833318
--- /dev/null
+++ b/tests/feature/Rules/FiniteTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::finite()->assert(''),
+ '"" must be a finite number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::finite())->assert(10),
+ '10 must not be a finite number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::finite()->assert([12]),
+ '- `[12]` must be a finite number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::finite())->assert('123456'),
+ '- "123456" must not be a finite number',
+));
diff --git a/tests/feature/Rules/FloatTypeTest.php b/tests/feature/Rules/FloatTypeTest.php
new file mode 100644
index 00000000..46859d0c
--- /dev/null
+++ b/tests/feature/Rules/FloatTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::floatType()->assert('42.33'),
+ '"42.33" must be float',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::floatType())->assert(INF),
+ '`INF` must not be float',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::floatType()->assert(true),
+ '- `true` must be float',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::floatType())->assert(2.0),
+ '- 2.0 must not be float',
+));
diff --git a/tests/feature/Rules/FloatvalTest.php b/tests/feature/Rules/FloatvalTest.php
new file mode 100644
index 00000000..408b6bc1
--- /dev/null
+++ b/tests/feature/Rules/FloatvalTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::floatVal()->assert('a'),
+ '"a" must be a float value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::floatVal())->assert(165.0),
+ '165.0 must not be a float value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::floatVal()->assert('a'),
+ '- "a" must be a float value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::floatVal())->assert('165.7'),
+ '- "165.7" must not be a float value',
+));
diff --git a/tests/feature/Rules/GraphTest.php b/tests/feature/Rules/GraphTest.php
new file mode 100644
index 00000000..a98d9025
--- /dev/null
+++ b/tests/feature/Rules/GraphTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::graph()->assert("foo\nbar"),
+ '"foo\\nbar" must contain only graphical characters',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::graph('foo')->assert("foo\nbar"),
+ '"foo\\nbar" must contain only graphical characters and "foo"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::graph())->assert('foobar'),
+ '"foobar" must not contain graphical characters',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::graph("\n"))->assert("foo\nbar"),
+ '"foo\\nbar" must not contain graphical characters or "\\n"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::graph()->assert("foo\nbar"),
+ '- "foo\\nbar" must contain only graphical characters',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::graph('foo')->assert("foo\nbar"),
+ '- "foo\\nbar" must contain only graphical characters and "foo"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::graph())->assert('foobar'),
+ '- "foobar" must not contain graphical characters',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::graph("\n"))->assert("foo\nbar"),
+ '- "foo\\nbar" must not contain graphical characters or "\\n"',
+));
diff --git a/tests/feature/Rules/GreaterThanOrEqualTest.php b/tests/feature/Rules/GreaterThanOrEqualTest.php
new file mode 100644
index 00000000..385302d8
--- /dev/null
+++ b/tests/feature/Rules/GreaterThanOrEqualTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::greaterThanOrEqual(INF)->assert(10),
+ '10 must be greater than or equal to `INF`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::greaterThanOrEqual(5))->assert(INF),
+ '`INF` must be less than 5',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::greaterThanOrEqual('today')->assert('yesterday'),
+ '- "yesterday" must be greater than or equal to "today"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::greaterThanOrEqual('a'))->assert('z'),
+ '- "z" must be less than "a"',
+));
diff --git a/tests/feature/Rules/GreaterThanTest.php b/tests/feature/Rules/GreaterThanTest.php
new file mode 100644
index 00000000..a70153fc
--- /dev/null
+++ b/tests/feature/Rules/GreaterThanTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::greaterThan(21)->assert(12),
+ '12 must be greater than 21',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::greaterThan('yesterday'))->assert('today'),
+ '"today" must not be greater than "yesterday"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::greaterThan('2018-09-09')->assert('1988-09-09'),
+ '- "1988-09-09" must be greater than "2018-09-09"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::greaterThan('a'))->assert('ba'),
+ '- "ba" must not be greater than "a"',
+));
diff --git a/tests/feature/Rules/HetuTest.php b/tests/feature/Rules/HetuTest.php
new file mode 100644
index 00000000..7120a7b1
--- /dev/null
+++ b/tests/feature/Rules/HetuTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::hetu()->assert('010106A901O'),
+ '"010106A901O" must be a valid Finnish personal identity code',
+ '- "010106A901O" must be a valid Finnish personal identity code',
+ ['hetu' => '"010106A901O" must be a valid Finnish personal identity code']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::hetu())->assert('010106A9012'),
+ '"010106A9012" must not be a valid Finnish personal identity code',
+ '- "010106A9012" must not be a valid Finnish personal identity code',
+ ['notHetu' => '"010106A9012" must not be a valid Finnish personal identity code']
+));
+
+test('With template', expectAll(
+ fn() => v::hetu()->assert('010106A901O', 'That is not a HETU'),
+ 'That is not a HETU',
+ '- That is not a HETU',
+ ['hetu' => 'That is not a HETU']
+));
+
+test('With name', expectAll(
+ fn() => v::hetu()->setName('Hetu')->assert('010106A901O'),
+ 'Hetu must be a valid Finnish personal identity code',
+ '- Hetu must be a valid Finnish personal identity code',
+ ['hetu' => 'Hetu must be a valid Finnish personal identity code']
+));
diff --git a/tests/feature/Rules/HexRgbColorTest.php b/tests/feature/Rules/HexRgbColorTest.php
new file mode 100644
index 00000000..2c2106ad
--- /dev/null
+++ b/tests/feature/Rules/HexRgbColorTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::hexRgbColor()->assert('invalid'),
+ '"invalid" must be a hex RGB color',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::hexRgbColor())->assert('#808080'),
+ '"#808080" must not be a hex RGB color',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::hexRgbColor()->assert('invalid'),
+ '- "invalid" must be a hex RGB color',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::hexRgbColor())->assert('#808080'),
+ '- "#808080" must not be a hex RGB color',
+));
diff --git a/tests/feature/Rules/IbanTest.php b/tests/feature/Rules/IbanTest.php
new file mode 100644
index 00000000..d164df78
--- /dev/null
+++ b/tests/feature/Rules/IbanTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::iban()->assert('SE35 5000 5880 7742'),
+ '"SE35 5000 5880 7742" must be a valid IBAN',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::iban())->assert('GB82 WEST 1234 5698 7654 32'),
+ '"GB82 WEST 1234 5698 7654 32" must not be a valid IBAN',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::iban()->assert('NOT AN IBAN'),
+ '- "NOT AN IBAN" must be a valid IBAN',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::iban())->assert('HU93 1160 0006 0000 0000 1234 5676'),
+ '- "HU93 1160 0006 0000 0000 1234 5676" must not be a valid IBAN',
+));
diff --git a/tests/feature/Rules/IdenticalTest.php b/tests/feature/Rules/IdenticalTest.php
new file mode 100644
index 00000000..14259b06
--- /dev/null
+++ b/tests/feature/Rules/IdenticalTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::identical(123)->assert(321),
+ '321 must be identical to 123',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::identical(321))->assert(321),
+ '321 must not be identical to 321',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::identical(123)->assert(321),
+ '- 321 must be identical to 123',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::identical(321))->assert(321),
+ '- 321 must not be identical to 321',
+));
diff --git a/tests/feature/Rules/ImageTest.php b/tests/feature/Rules/ImageTest.php
new file mode 100644
index 00000000..3e720f6b
--- /dev/null
+++ b/tests/feature/Rules/ImageTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::image()->assert('tests/fixtures/invalid-image.png'),
+ '"tests/fixtures/invalid-image.png" must be a valid image file',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::image())->assert('tests/fixtures/valid-image.png'),
+ '"tests/fixtures/valid-image.png" must not be a valid image file',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::image()->assert(new stdClass()),
+ '- `stdClass {}` must be a valid image file',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::image())->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must not be a valid image file',
+));
diff --git a/tests/feature/Rules/ImeiTest.php b/tests/feature/Rules/ImeiTest.php
new file mode 100644
index 00000000..6335888f
--- /dev/null
+++ b/tests/feature/Rules/ImeiTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::imei()->assert('490154203237512'),
+ '"490154203237512" must be a valid IMEI number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::imei())->assert('350077523237513'),
+ '"350077523237513" must not be a valid IMEI number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::imei()->assert(null),
+ '- `null` must be a valid IMEI number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::imei())->assert('356938035643809'),
+ '- "356938035643809" must not be a valid IMEI number',
+));
diff --git a/tests/feature/Rules/InTest.php b/tests/feature/Rules/InTest.php
new file mode 100644
index 00000000..c002e87b
--- /dev/null
+++ b/tests/feature/Rules/InTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::in([3, 2])->assert(1),
+ '1 must be in `[3, 2]`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::in('foobar'))->assert('foo'),
+ '"foo" must not be in "foobar"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::in([2, '1', 3], true)->assert('2'),
+ '- "2" must be in `[2, "1", 3]`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::in([2, '1', 3], true))->assert('1'),
+ '- "1" must not be in `[2, "1", 3]`',
+));
diff --git a/tests/feature/Rules/InfiniteTest.php b/tests/feature/Rules/InfiniteTest.php
new file mode 100644
index 00000000..66304c1f
--- /dev/null
+++ b/tests/feature/Rules/InfiniteTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::infinite()->assert(-9),
+ '-9 must be an infinite number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::infinite())->assert(INF),
+ '`INF` must not be an infinite number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::infinite()->assert(new stdClass()),
+ '- `stdClass {}` must be an infinite number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::infinite())->assert(INF * -1),
+ '- `-INF` must not be an infinite number',
+));
diff --git a/tests/feature/Rules/InstanceTest.php b/tests/feature/Rules/InstanceTest.php
new file mode 100644
index 00000000..fe503b8b
--- /dev/null
+++ b/tests/feature/Rules/InstanceTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::instance(DateTime::class)->assert(''),
+ '"" must be an instance of `DateTime`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::instance(Traversable::class))->assert(new ArrayObject()),
+ '`ArrayObject { getArrayCopy() => [] }` must not be an instance of `Traversable`',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::instance(ArrayIterator::class)->assert(new stdClass()),
+ '- `stdClass {}` must be an instance of `ArrayIterator`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::instance(stdClass::class))->assert(new stdClass()),
+ '- `stdClass {}` must not be an instance of `stdClass`',
+));
diff --git a/tests/feature/Rules/IntTypeTest.php b/tests/feature/Rules/IntTypeTest.php
new file mode 100644
index 00000000..0636b381
--- /dev/null
+++ b/tests/feature/Rules/IntTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::intType()->assert(new stdClass()),
+ '`stdClass {}` must be an integer',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::intType())->assert(42),
+ '42 must not be an integer',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::intType()->assert(INF),
+ '- `INF` must be an integer',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::intType())->assert(1234567890),
+ '- 1234567890 must not be an integer',
+));
diff --git a/tests/feature/Rules/IntValTest.php b/tests/feature/Rules/IntValTest.php
new file mode 100644
index 00000000..855fb98b
--- /dev/null
+++ b/tests/feature/Rules/IntValTest.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::intVal()->assert('42.33'),
+ '"42.33" must be an integer value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::intVal())->assert(2),
+ '2 must not be an integer value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::intVal()->assert('Foo'),
+ '- "Foo" must be an integer value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::intVal())->assert(3),
+ '- 3 must not be an integer value',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::not(v::intVal())->assert(-42),
+ '- -42 must not be an integer value',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::intVal())->assert('-42'),
+ '- "-42" must not be an integer value',
+));
diff --git a/tests/feature/Rules/IpTest.php b/tests/feature/Rules/IpTest.php
new file mode 100644
index 00000000..e11f667a
--- /dev/null
+++ b/tests/feature/Rules/IpTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::ip()->assert('257.0.0.1'),
+ '"257.0.0.1" must be an IP address',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::ip())->assert('127.0.0.1'),
+ '"127.0.0.1" must not be an IP address',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::ip('127.0.1.*')->assert('127.0.0.1'),
+ '"127.0.0.1" must be an IP address in the 127.0.1.0-127.0.1.255 range',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::ip('127.0.1.*'))->assert('127.0.1.1'),
+ '"127.0.1.1" must not be an IP address in the 127.0.1.0-127.0.1.255 range',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::ip()->assert('257.0.0.1'),
+ '- "257.0.0.1" must be an IP address',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::ip())->assert('127.0.0.1'),
+ '- "127.0.0.1" must not be an IP address',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::ip('127.0.1.*')->assert('127.0.0.1'),
+ '- "127.0.0.1" must be an IP address in the 127.0.1.0-127.0.1.255 range',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::ip('127.0.1.*'))->assert('127.0.1.1'),
+ '- "127.0.1.1" must not be an IP address in the 127.0.1.0-127.0.1.255 range',
+));
diff --git a/tests/feature/Rules/IsbnTest.php b/tests/feature/Rules/IsbnTest.php
new file mode 100644
index 00000000..27824c51
--- /dev/null
+++ b/tests/feature/Rules/IsbnTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::isbn()->assert('ISBN-12: 978-0-596-52068-7'),
+ '"ISBN-12: 978-0-596-52068-7" must be a valid ISBN',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::isbn())->assert('ISBN-13: 978-0-596-52068-7'),
+ '"ISBN-13: 978-0-596-52068-7" must not be a valid ISBN',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::isbn()->assert('978 10 596 52068 7'),
+ '- "978 10 596 52068 7" must be a valid ISBN',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::isbn())->assert('978 0 596 52068 7'),
+ '- "978 0 596 52068 7" must not be a valid ISBN',
+));
diff --git a/tests/feature/Rules/IterableTypeTest.php b/tests/feature/Rules/IterableTypeTest.php
new file mode 100644
index 00000000..46fac32b
--- /dev/null
+++ b/tests/feature/Rules/IterableTypeTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::iterableType()->assert(null),
+ '`null` must be iterable',
+ '- `null` must be iterable',
+ ['iterableType' => '`null` must be iterable']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::iterableType())->assert([1, 2, 3]),
+ '`[1, 2, 3]` must not iterable',
+ '- `[1, 2, 3]` must not iterable',
+ ['notIterableType' => '`[1, 2, 3]` must not iterable']
+));
+
+test('With template', expectAll(
+ fn() => v::iterableType()->assert(null, 'Not an iterable at all'),
+ 'Not an iterable at all',
+ '- Not an iterable at all',
+ ['iterableType' => 'Not an iterable at all']
+));
+
+test('With name', expectAll(
+ fn() => v::iterableType()->setName('Options')->assert(null),
+ 'Options must be iterable',
+ '- Options must be iterable',
+ ['iterableType' => 'Options must be iterable']
+));
diff --git a/tests/feature/Rules/IterableValTest.php b/tests/feature/Rules/IterableValTest.php
new file mode 100644
index 00000000..5b1dd537
--- /dev/null
+++ b/tests/feature/Rules/IterableValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::iterableVal()->assert(3),
+ '3 must be an iterable value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::iterableVal())->assert([2, 3]),
+ '`[2, 3]` must not be an iterable value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::iterableVal()->assert('String'),
+ '- "String" must be an iterable value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::iterableVal())->assert(new stdClass()),
+ '- `stdClass {}` must not be an iterable value',
+));
diff --git a/tests/feature/Rules/JsonTest.php b/tests/feature/Rules/JsonTest.php
new file mode 100644
index 00000000..0c90e666
--- /dev/null
+++ b/tests/feature/Rules/JsonTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::json()->assert(false),
+ '`false` must be a valid JSON string',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::json())->assert('{"foo": "bar", "number":1}'),
+ '"{\\"foo\\": \\"bar\\", \\"number\\":1}" must not be a valid JSON string',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::json()->assert(new stdClass()),
+ '- `stdClass {}` must be a valid JSON string',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::json())->assert('{}'),
+ '- "{}" must not be a valid JSON string',
+));
diff --git a/tests/feature/Rules/KeyExistsTest.php b/tests/feature/Rules/KeyExistsTest.php
new file mode 100644
index 00000000..209ded4d
--- /dev/null
+++ b/tests/feature/Rules/KeyExistsTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default mode', expectAll(
+ fn() => v::keyExists('foo')->assert(['bar' => 'baz']),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('Inverted mode', expectAll(
+ fn() => v::not(v::keyExists('foo'))->assert(['foo' => 'baz']),
+ 'foo must not be present',
+ '- foo must not be present',
+ ['foo' => 'foo must not be present']
+));
+
+test('Custom name', expectAll(
+ fn() => v::keyExists('foo')->setName('Custom name')->assert(['bar' => 'baz']),
+ 'Custom name must be present',
+ '- Custom name must be present',
+ ['foo' => 'Custom name must be present']
+));
+
+test('Custom template', expectAll(
+ fn() => v::keyExists('foo')->assert(['bar' => 'baz'], 'Custom template for `{{name}}`'),
+ 'Custom template for `foo`',
+ '- Custom template for `foo`',
+ ['foo' => 'Custom template for `foo`']
+));
diff --git a/tests/feature/Rules/KeyOptionalTest.php b/tests/feature/Rules/KeyOptionalTest.php
new file mode 100644
index 00000000..cb75c613
--- /dev/null
+++ b/tests/feature/Rules/KeyOptionalTest.php
@@ -0,0 +1,78 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::keyOptional('foo', v::intType())->assert(['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::keyOptional('foo', v::intType()))->assert(['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('Inverted with missing key', expectAll(
+ fn() => v::not(v::keyOptional('foo', v::intType()))->assert([]),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('With wrapped name, default', expectAll(
+ fn() => v::keyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')->assert(['foo' => 'string']),
+ 'Wrapped must be an integer',
+ '- Wrapped must be an integer',
+ ['foo' => 'Wrapped must be an integer']
+));
+
+test('With wrapped name, inverted', expectAll(
+ fn() => v::not(v::keyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not')->assert(['foo' => 12]),
+ 'Wrapped must not be an integer',
+ '- Wrapped must not be an integer',
+ ['foo' => 'Wrapped must not be an integer']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::keyOptional('foo', v::intType())->setName('Wrapper')->assert(['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::keyOptional('foo', v::intType())->setName('Wrapper'))->setName('Not')->assert(['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With "Not" name, inverted', expectAll(
+ fn() => v::not(v::keyOptional('foo', v::intType()))->setName('Not')->assert(['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With template, default', expectAll(
+ fn() => v::keyOptional('foo', v::intType())->assert(['foo' => 'string'], 'That key is off-key'),
+ 'That key is off-key',
+ '- That key is off-key',
+ ['foo' => 'That key is off-key']
+));
+
+test('With template, inverted', expectAll(
+ fn() => v::not(v::keyOptional('foo', v::intType()))->assert(['foo' => 12], 'No off-key key'),
+ 'No off-key key',
+ '- No off-key key',
+ ['foo' => 'No off-key key']
+));
diff --git a/tests/feature/Rules/KeySetTest.php b/tests/feature/Rules/KeySetTest.php
new file mode 100644
index 00000000..836759cd
--- /dev/null
+++ b/tests/feature/Rules/KeySetTest.php
@@ -0,0 +1,215 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('one rule / one failed', expectAll(
+ fn() => v::keySet(v::key('foo', v::intType()))->assert(['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('one rule / one missing key', expectAll(
+ fn() => v::keySet(v::keyExists('foo'))->assert([]),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('one rule / one extra key', expectAll(
+ fn() => v::keySet(v::keyExists('foo'))->assert(['foo' => 42, 'bar' => 'string']),
+ 'bar must not be present',
+ '- bar must not be present',
+ ['bar' => 'bar must not be present']
+));
+
+test('one rule / one extra key / one missing key', expectAll(
+ fn() => v::keySet(v::keyExists('foo'))->assert(['bar' => true]),
+ 'foo must be present',
+ <<<'FULL_MESSAGE'
+ - `["bar": true]` contains both missing and extra keys
+ - foo must be present
+ - bar must not be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["bar": true]` contains both missing and extra keys',
+ 'foo' => 'foo must be present',
+ 'bar' => 'bar must not be present',
+ ]
+));
+
+test('one rule / two extra keys', expectAll(
+ fn() => v::keySet(v::keyExists('foo'))->assert(['foo' => 42, 'bar' => 'string', 'baz' => true]),
+ 'bar must not be present',
+ <<<'FULL_MESSAGE'
+ - `["foo": 42, "bar": "string", "baz": true]` contains extra keys
+ - bar must not be present
+ - baz must not be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["foo": 42, "bar": "string", "baz": true]` contains extra keys',
+ 'bar' => 'bar must not be present',
+ 'baz' => 'baz must not be present',
+ ]
+));
+
+test('one rule / more than ten extra keys', expectAll(
+ fn() => v::keySet(v::keyExists('foo'))
+ ->assert([
+ 'foo' => 42,
+ 'bar' => 'string',
+ 'baz' => true,
+ 'qux' => false,
+ 'quux' => 42,
+ 'corge' => 'string',
+ 'grault' => true,
+ 'garply' => false,
+ 'waldo' => 42,
+ 'fred' => 'string',
+ 'plugh' => true,
+ 'xyzzy' => false,
+ 'thud' => 42,
+ ]),
+ 'bar must not be present',
+ <<<'FULL_MESSAGE'
+ - `["foo": 42, "bar": "string", "baz": true, "qux": false, "quux": 42, ...]` contains extra keys
+ - bar must not be present
+ - baz must not be present
+ - qux must not be present
+ - quux must not be present
+ - corge must not be present
+ - grault must not be present
+ - garply must not be present
+ - waldo must not be present
+ - fred must not be present
+ - plugh must not be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["foo": 42, "bar": "string", "baz": true, "qux": false, "quux": 42, ...]` contains extra keys',
+ 'bar' => 'bar must not be present',
+ 'baz' => 'baz must not be present',
+ 'qux' => 'qux must not be present',
+ 'quux' => 'quux must not be present',
+ 'corge' => 'corge must not be present',
+ 'grault' => 'grault must not be present',
+ 'garply' => 'garply must not be present',
+ 'waldo' => 'waldo must not be present',
+ 'fred' => 'fred must not be present',
+ 'plugh' => 'plugh must not be present',
+ ]
+));
+
+test('multiple rules / one failed', expectAll(
+ fn() => v::keySet(v::keyExists('foo'), v::keyExists('bar'))->assert(['foo' => 42]),
+ 'bar must be present',
+ '- bar must be present',
+ ['bar' => 'bar must be present']
+));
+
+test('multiple rules / all failed', expectAll(
+ fn() => v::keySet(v::keyExists('foo'), v::keyExists('bar'))->assert([]),
+ 'foo must be present',
+ <<<'FULL_MESSAGE'
+ - `[]` contains missing keys
+ - foo must be present
+ - bar must be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`[]` contains missing keys',
+ 'foo' => 'foo must be present',
+ 'bar' => 'bar must be present',
+ ]
+));
+
+test('multiple rules / one extra key', expectAll(
+ fn() => v::keySet(
+ v::keyExists('foo'),
+ v::keyExists('bar')
+ )->assert(['foo' => 42, 'bar' => 'string', 'baz' => true]),
+ 'baz must not be present',
+ '- baz must not be present',
+ ['baz' => 'baz must not be present']
+));
+
+test('multiple rules / one extra key / one missing', expectAll(
+ fn() => v::keySet(
+ v::keyExists('foo'),
+ v::keyExists('bar')
+ )->assert(['bar' => 'string', 'baz' => true]),
+ 'foo must be present',
+ <<<'FULL_MESSAGE'
+ - `["bar": "string", "baz": true]` contains both missing and extra keys
+ - foo must be present
+ - baz must not be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["bar": "string", "baz": true]` contains both missing and extra keys',
+ 'foo' => 'foo must be present',
+ 'baz' => 'baz must not be present',
+ ]
+));
+
+test('multiple rules / two extra keys', expectAll(
+ fn() => v::keySet(
+ v::keyExists('foo'),
+ v::keyExists('bar'),
+ v::keyOptional('qux', v::intType())
+ )->assert(['foo' => 42, 'bar' => 'string', 'baz' => true, 'qux' => false]),
+ 'qux must be an integer',
+ <<<'FULL_MESSAGE'
+ - `["foo": 42, "bar": "string", "baz": true, "qux": false]` contains extra keys
+ - qux must be an integer
+ - baz must not be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["foo": 42, "bar": "string", "baz": true, "qux": false]` contains extra keys',
+ 'qux' => 'qux must be an integer',
+ 'baz' => 'baz must not be present',
+ ]
+));
+
+test('multiple rules / all failed validation', expectAll(
+ fn() => v::keySet(
+ v::key('foo', v::intType()),
+ v::key('bar', v::intType()),
+ v::key('baz', v::intType())
+ )
+ ->assert(['foo' => 42, 'bar' => 'string', 'baz' => true]),
+ 'bar must be an integer',
+ <<<'FULL_MESSAGE'
+ - `["foo": 42, "bar": "string", "baz": true]` validation failed
+ - bar must be an integer
+ - baz must be an integer
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["foo": 42, "bar": "string", "baz": true]` validation failed',
+ 'bar' => 'bar must be an integer',
+ 'baz' => 'baz must be an integer',
+ ]
+));
+
+test('multiple rules / single missing key / single failed validation', expectAll(
+ fn() => v::keySet(
+ v::create()
+ ->key('foo', v::intType())
+ ->key('bar', v::intType())
+ ->key('baz', v::intType())
+ )
+ ->assert(['foo' => 42, 'bar' => 'string']),
+ 'bar must be an integer',
+ <<<'FULL_MESSAGE'
+ - `["foo": 42, "bar": "string"]` contains missing keys
+ - bar must be an integer
+ - baz must be present
+ FULL_MESSAGE,
+ [
+ '__root__' => '`["foo": 42, "bar": "string"]` contains missing keys',
+ 'bar' => 'bar must be an integer',
+ 'baz' => 'baz must be present',
+ ]
+));
diff --git a/tests/feature/Rules/KeyTest.php b/tests/feature/Rules/KeyTest.php
new file mode 100644
index 00000000..e9457f7c
--- /dev/null
+++ b/tests/feature/Rules/KeyTest.php
@@ -0,0 +1,99 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Missing key', expectAll(
+ fn() => v::key('foo', v::intType())->assert([]),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('Default', expectAll(
+ fn() => v::key('foo', v::intType())->assert(['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::key('foo', v::intType()))->assert(['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('Double-inverted with missing key', expectAll(
+ fn() => v::not(v::not(v::key('foo', v::intType())))->assert([]),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('With wrapped name, missing key', expectAll(
+ fn() => v::key('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')->assert([]),
+ 'Wrapped must be present',
+ '- Wrapped must be present',
+ ['foo' => 'Wrapped must be present']
+));
+
+test('With wrapped name, default', expectAll(
+ fn() => v::key('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')->assert(['foo' => 'string']),
+ 'Wrapped must be an integer',
+ '- Wrapped must be an integer',
+ ['foo' => 'Wrapped must be an integer']
+));
+
+test('With wrapped name, inverted', expectAll(
+ fn() => v::not(v::key('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not')->assert(['foo' => 12]),
+ 'Wrapped must not be an integer',
+ '- Wrapped must not be an integer',
+ ['foo' => 'Wrapped must not be an integer']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::key('foo', v::intType())->setName('Wrapper')->assert(['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('With wrapper name, missing key', expectAll(
+ fn() => v::key('foo', v::intType())->setName('Wrapper')->assert([]),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::key('foo', v::intType())->setName('Wrapper'))->setName('Not')->assert(['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With "Not" name, inverted', expectAll(
+ fn() => v::not(v::key('foo', v::intType()))->setName('Not')->assert(['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With template, default', expectAll(
+ fn() => v::key('foo', v::intType())->assert(['foo' => 'string'], 'That key is off-key'),
+ 'That key is off-key',
+ '- That key is off-key',
+ ['foo' => 'That key is off-key']
+));
+
+test('With template, inverted', expectAll(
+ fn() => v::not(v::key('foo', v::intType()))->assert(['foo' => 12], 'No off-key key'),
+ 'No off-key key',
+ '- No off-key key',
+ ['foo' => 'No off-key key']
+));
diff --git a/tests/feature/Rules/LanguageCodeTest.php b/tests/feature/Rules/LanguageCodeTest.php
new file mode 100644
index 00000000..2ea3ad95
--- /dev/null
+++ b/tests/feature/Rules/LanguageCodeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::languageCode()->assert(null),
+ '`null` must be a valid language code',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::languageCode())->assert('pt'),
+ '"pt" must not be a valid language code',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::languageCode()->assert('por'),
+ '- "por" must be a valid language code',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::languageCode())->assert('en'),
+ '- "en" must not be a valid language code',
+));
diff --git a/tests/feature/Rules/LazyTest.php b/tests/feature/Rules/LazyTest.php
new file mode 100644
index 00000000..8b99ddae
--- /dev/null
+++ b/tests/feature/Rules/LazyTest.php
@@ -0,0 +1,64 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::lazy(fn() => v::intType())->assert(true),
+ '`true` must be an integer',
+ '- `true` must be an integer',
+ ['intType' => '`true` must be an integer']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::lazy(fn() => v::intType()))->assert(2),
+ '2 must not be an integer',
+ '- 2 must not be an integer',
+ ['notIntType' => '2 must not be an integer']
+));
+
+test('With created name, default', expectAll(
+ fn() => v::lazy(fn() => v::intType()->setName('Created'))->setName('Wrapper')->assert(true),
+ 'Created must be an integer',
+ '- Created must be an integer',
+ ['intType' => 'Created must be an integer']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::lazy(fn() => v::intType())->setName('Wrapper')->assert(true),
+ 'Wrapper must be an integer',
+ '- Wrapper must be an integer',
+ ['intType' => 'Wrapper must be an integer']
+));
+
+test('With created name, inverted', expectAll(
+ fn() => v::not(v::lazy(fn() => v::intType()->setName('Created'))->setName('Wrapped'))->setName('Not')->assert(2),
+ 'Created must not be an integer',
+ '- Created must not be an integer',
+ ['notIntType' => 'Created must not be an integer']
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::lazy(fn() => v::intType())->setName('Wrapped'))->setName('Not')->assert(2),
+ 'Wrapped must not be an integer',
+ '- Wrapped must not be an integer',
+ ['notIntType' => 'Wrapped must not be an integer']
+));
+
+test('With not name, inverted', expectAll(
+ fn() => v::not(v::lazy(fn() => v::intType()))->setName('Not')->assert(2),
+ 'Not must not be an integer',
+ '- Not must not be an integer',
+ ['notIntType' => 'Not must not be an integer']
+));
+
+test('With template, default', expectAll(
+ fn() => v::lazy(fn() => v::intType())->assert(true, 'Lazy lizards lounging like lords in the local lagoon'),
+ 'Lazy lizards lounging like lords in the local lagoon',
+ '- Lazy lizards lounging like lords in the local lagoon',
+ ['intType' => 'Lazy lizards lounging like lords in the local lagoon']
+));
diff --git a/tests/feature/Rules/LeapDateTest.php b/tests/feature/Rules/LeapDateTest.php
new file mode 100644
index 00000000..40de905e
--- /dev/null
+++ b/tests/feature/Rules/LeapDateTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::leapDate('Y-m-d')->assert('1989-02-29'),
+ '"1989-02-29" must be a valid leap date',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::leapDate('Y-m-d'))->assert('1988-02-29'),
+ '"1988-02-29" must not be a leap date',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::leapDate('Y-m-d')->assert('1990-02-29'),
+ '- "1990-02-29" must be a valid leap date',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::leapDate('Y-m-d'))->assert('1992-02-29'),
+ '- "1992-02-29" must not be a leap date',
+));
diff --git a/tests/feature/Rules/LeapYearTest.php b/tests/feature/Rules/LeapYearTest.php
new file mode 100644
index 00000000..a4469e9f
--- /dev/null
+++ b/tests/feature/Rules/LeapYearTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::leapYear()->assert('2009'),
+ '"2009" must be a valid leap year',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::leapYear())->assert('2008'),
+ '"2008" must not be a leap year',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::leapYear()->assert('2009-02-29'),
+ '- "2009-02-29" must be a valid leap year',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::leapYear())->assert('2008'),
+ '- "2008" must not be a leap year',
+));
diff --git a/tests/feature/Rules/LengthTest.php b/tests/feature/Rules/LengthTest.php
new file mode 100644
index 00000000..6c2f981b
--- /dev/null
+++ b/tests/feature/Rules/LengthTest.php
@@ -0,0 +1,43 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::length(v::equals(3))->assert('tulip'),
+ 'The length of "tulip" must be equal to 3',
+ '- The length of "tulip" must be equal to 3',
+ ['lengthEquals' => 'The length of "tulip" must be equal to 3']
+));
+
+test('Inverted wrapped', expectAll(
+ fn() => v::length(v::not(v::equals(4)))->assert('rose'),
+ 'The length of "rose" must not be equal to 4',
+ '- The length of "rose" must not be equal to 4',
+ ['lengthNotEquals' => 'The length of "rose" must not be equal to 4']
+));
+
+test('Inverted wrapper', expectAll(
+ fn() => v::not(v::length(v::equals(4)))->assert('fern'),
+ 'The length of "fern" must not be equal to 4',
+ '- The length of "fern" must not be equal to 4',
+ ['notLengthEquals' => 'The length of "fern" must not be equal to 4']
+));
+
+test('With template', expectAll(
+ fn() => v::length(v::equals(3))->assert('azalea', 'This is a template'),
+ 'This is a template',
+ '- This is a template',
+ ['lengthEquals' => 'This is a template']
+));
+
+test('With wrapper name', expectAll(
+ fn() => v::length(v::equals(3))->setName('Cactus')->assert('peyote'),
+ 'The length of Cactus must be equal to 3',
+ '- The length of Cactus must be equal to 3',
+ ['lengthEquals' => 'The length of Cactus must be equal to 3']
+));
diff --git a/tests/feature/Rules/LessThanOrEqualTest.php b/tests/feature/Rules/LessThanOrEqualTest.php
new file mode 100644
index 00000000..ce8838c2
--- /dev/null
+++ b/tests/feature/Rules/LessThanOrEqualTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::lessThanOrEqual(10)->assert(11),
+ '11 must be less than or equal to 10',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::lessThanOrEqual(10))->assert(5),
+ '5 must be greater than 10',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::lessThanOrEqual('today')->assert('tomorrow'),
+ '- "tomorrow" must be less than or equal to "today"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::lessThanOrEqual('b'))->assert('a'),
+ '- "a" must be greater than "b"',
+));
diff --git a/tests/feature/Rules/LessThanTest.php b/tests/feature/Rules/LessThanTest.php
new file mode 100644
index 00000000..a88634f2
--- /dev/null
+++ b/tests/feature/Rules/LessThanTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::lessThan(12)->assert(21),
+ '21 must be less than 12',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::lessThan('today'))->assert('yesterday'),
+ '"yesterday" must not be less than "today"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::lessThan('1988-09-09')->assert('2018-09-09'),
+ '- "2018-09-09" must be less than "1988-09-09"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::lessThan('b'))->assert('a'),
+ '- "a" must not be less than "b"',
+));
diff --git a/tests/feature/Rules/LowercaseTest.php b/tests/feature/Rules/LowercaseTest.php
new file mode 100644
index 00000000..fa32670f
--- /dev/null
+++ b/tests/feature/Rules/LowercaseTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::lowercase()->assert('UPPERCASE'),
+ '"UPPERCASE" must contain only lowercase letters',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::lowercase())->assert('lowercase'),
+ '"lowercase" must not contain only lowercase letters',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::lowercase()->assert('UPPERCASE'),
+ '- "UPPERCASE" must contain only lowercase letters',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::lowercase())->assert('lowercase'),
+ '- "lowercase" must not contain only lowercase letters',
+));
diff --git a/tests/feature/Rules/LuhnTest.php b/tests/feature/Rules/LuhnTest.php
new file mode 100644
index 00000000..fac976f0
--- /dev/null
+++ b/tests/feature/Rules/LuhnTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::luhn()->assert('2222400041240021'),
+ '"2222400041240021" must be a valid Luhn number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::luhn())->assert('2223000048400011'),
+ '"2223000048400011" must not be a valid Luhn number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::luhn()->assert('340316193809334'),
+ '- "340316193809334" must be a valid Luhn number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::luhn())->assert('6011000990139424'),
+ '- "6011000990139424" must not be a valid Luhn number',
+));
diff --git a/tests/feature/Rules/MacAddressTest.php b/tests/feature/Rules/MacAddressTest.php
new file mode 100644
index 00000000..b0e068c3
--- /dev/null
+++ b/tests/feature/Rules/MacAddressTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::macAddress()->assert('00-11222:33:44:55'),
+ '"00-11222:33:44:55" must be a valid MAC address',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::macAddress())->assert('00:11:22:33:44:55'),
+ '"00:11:22:33:44:55" must not be a valid MAC address',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::macAddress()->assert('90-bc-nk:1a-dd-cc'),
+ '- "90-bc-nk:1a-dd-cc" must be a valid MAC address',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::macAddress())->assert('AF:0F:bd:12:44:ba'),
+ '- "AF:0F:bd:12:44:ba" must not be a valid MAC address',
+));
diff --git a/tests/feature/Rules/MaxTest.php b/tests/feature/Rules/MaxTest.php
new file mode 100644
index 00000000..89dd95f6
--- /dev/null
+++ b/tests/feature/Rules/MaxTest.php
@@ -0,0 +1,71 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Non-iterable', expectAll(
+ fn() => v::max(v::negative())->assert(null),
+ '`null` must be iterable',
+ '- `null` must be iterable',
+ ['max' => '`null` must be iterable']
+));
+
+test('Empty', expectAll(
+ fn() => v::max(v::negative())->assert([]),
+ 'The value must not be empty',
+ '- The value must not be empty',
+ ['max' => 'The value must not be empty']
+));
+
+test('Default', expectAll(
+ fn() => v::max(v::negative())->assert([1, 2, 3]),
+ 'As the maximum of `[1, 2, 3]`, 3 must be a negative number',
+ '- As the maximum of `[1, 2, 3]`, 3 must be a negative number',
+ ['maxNegative' => 'As the maximum of `[1, 2, 3]`, 3 must be a negative number']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::max(v::negative()))->assert([-3, -2, -1]),
+ 'As the maximum of `[-3, -2, -1]`, -1 must not be a negative number',
+ '- As the maximum of `[-3, -2, -1]`, -1 must not be a negative number',
+ ['notMaxNegative' => 'As the maximum of `[-3, -2, -1]`, -1 must not be a negative number']
+));
+
+test('With wrapped name, default', expectAll(
+ fn() => v::max(v::negative()->setName('Wrapped'))->setName('Wrapper')->assert([1, 2, 3]),
+ 'The maximum of Wrapped must be a negative number',
+ '- The maximum of Wrapped must be a negative number',
+ ['maxNegative' => 'The maximum of Wrapped must be a negative number']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::max(v::negative())->setName('Wrapper')->assert([1, 2, 3]),
+ 'The maximum of Wrapper must be a negative number',
+ '- The maximum of Wrapper must be a negative number',
+ ['maxNegative' => 'The maximum of Wrapper must be a negative number']
+));
+
+test('With wrapped name, inverted', expectAll(
+ fn() => v::not(v::max(v::negative()->setName('Wrapped')))->setName('Wrapper')->assert([-3, -2, -1]),
+ 'The maximum of Wrapped must not be a negative number',
+ '- The maximum of Wrapped must not be a negative number',
+ ['notMaxNegative' => 'The maximum of Wrapped must not be a negative number']
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::max(v::negative()))->setName('Wrapper')->assert([-3, -2, -1]),
+ 'The maximum of Wrapper must not be a negative number',
+ '- The maximum of Wrapper must not be a negative number',
+ ['notMaxNegative' => 'The maximum of Wrapper must not be a negative number']
+));
+
+test('With template, default', expectAll(
+ fn() => v::max(v::negative())->assert([1, 2, 3], 'The maximum of the value is not what we expect'),
+ 'The maximum of the value is not what we expect',
+ '- The maximum of the value is not what we expect',
+ ['maxNegative' => 'The maximum of the value is not what we expect']
+));
diff --git a/tests/feature/Rules/MimetypeTest.php b/tests/feature/Rules/MimetypeTest.php
new file mode 100644
index 00000000..40babbb2
--- /dev/null
+++ b/tests/feature/Rules/MimetypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::mimetype('image/png')->assert('image.png'),
+ '"image.png" must have the "image/png" MIME type',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::mimetype('image/png'))->assert('tests/fixtures/valid-image.png'),
+ '"tests/fixtures/valid-image.png" must not have the "image/png" MIME type',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::mimetype('image/png')->assert('tests/fixtures/invalid-image.png'),
+ '- "tests/fixtures/invalid-image.png" must have the "image/png" MIME type',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::mimetype('image/png'))->assert('tests/fixtures/valid-image.png'),
+ '- "tests/fixtures/valid-image.png" must not have the "image/png" MIME type',
+));
diff --git a/tests/feature/Rules/MinTest.php b/tests/feature/Rules/MinTest.php
new file mode 100644
index 00000000..17be7d04
--- /dev/null
+++ b/tests/feature/Rules/MinTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::min(v::equals(1))->assert([2, 3]),
+ 'As the minimum from `[2, 3]`, 2 must be equal to 1',
+ '- As the minimum from `[2, 3]`, 2 must be equal to 1',
+ ['minEquals' => 'As the minimum from `[2, 3]`, 2 must be equal to 1']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::min(v::equals(1)))->assert([1, 2, 3]),
+ 'As the minimum from `[1, 2, 3]`, 1 must not be equal to 1',
+ '- As the minimum from `[1, 2, 3]`, 1 must not be equal to 1',
+ ['notMinEquals' => 'As the minimum from `[1, 2, 3]`, 1 must not be equal to 1']
+));
+
+test('With template', expectAll(
+ fn() => v::min(v::equals(1))->assert([2, 3], 'That did not go as planned'),
+ 'That did not go as planned',
+ '- That did not go as planned',
+ ['minEquals' => 'That did not go as planned']
+));
+
+test('With name', expectAll(
+ fn() => v::min(v::equals(1))->setName('Options')->assert([2, 3]),
+ 'The minimum from Options must be equal to 1',
+ '- The minimum from Options must be equal to 1',
+ ['minEquals' => 'The minimum from Options must be equal to 1']
+));
diff --git a/tests/feature/Rules/MultipleTest.php b/tests/feature/Rules/MultipleTest.php
new file mode 100644
index 00000000..89ef6c02
--- /dev/null
+++ b/tests/feature/Rules/MultipleTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::multiple(3)->assert(22),
+ '22 must be a multiple of 3',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::multiple(3))->assert(9),
+ '9 must not be a multiple of 3',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::multiple(2)->assert(5),
+ '- 5 must be a multiple of 2',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::multiple(5))->assert(25),
+ '- 25 must not be a multiple of 5',
+));
diff --git a/tests/feature/Rules/NegativeTest.php b/tests/feature/Rules/NegativeTest.php
new file mode 100644
index 00000000..0d12fcda
--- /dev/null
+++ b/tests/feature/Rules/NegativeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::negative()->assert(16),
+ '16 must be a negative number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::negative())->assert(-10),
+ '-10 must not be a negative number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::negative()->assert('a'),
+ '- "a" must be a negative number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::negative())->assert('-144'),
+ '- "-144" must not be a negative number',
+));
diff --git a/tests/feature/Rules/NfeAccessKeyTest.php b/tests/feature/Rules/NfeAccessKeyTest.php
new file mode 100644
index 00000000..7a443162
--- /dev/null
+++ b/tests/feature/Rules/NfeAccessKeyTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::nfeAccessKey()->assert('31841136830118868211870485416765268625116906'),
+ '"31841136830118868211870485416765268625116906" must be a valid NFe access key',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::nfeAccessKey())->assert('52060433009911002506550120000007800267301615'),
+ '"52060433009911002506550120000007800267301615" must not be a valid NFe access key',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::nfeAccessKey()->assert('31841136830118868211870485416765268625116906'),
+ '- "31841136830118868211870485416765268625116906" must be a valid NFe access key',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::nfeAccessKey())->assert('52060433009911002506550120000007800267301615'),
+ '- "52060433009911002506550120000007800267301615" must not be a valid NFe access key',
+));
diff --git a/tests/feature/Rules/NifTest.php b/tests/feature/Rules/NifTest.php
new file mode 100644
index 00000000..fde912ac
--- /dev/null
+++ b/tests/feature/Rules/NifTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::nif()->assert('06357771Q'),
+ '"06357771Q" must be a valid NIF',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::nif())->assert('71110316C'),
+ '"71110316C" must not be a valid NIF',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::nif()->assert('06357771Q'),
+ '- "06357771Q" must be a valid NIF',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::nif())->assert('R1332622H'),
+ '- "R1332622H" must not be a valid NIF',
+));
diff --git a/tests/feature/Rules/NipTest.php b/tests/feature/Rules/NipTest.php
new file mode 100644
index 00000000..f59460df
--- /dev/null
+++ b/tests/feature/Rules/NipTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+require_once 'vendor/autoload.php';
+
+test('Scenario #1', expectMessage(
+ fn() => v::nip()->assert('1645865778'),
+ '"1645865778" must be a valid Polish VAT identification number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::nip())->assert('1645865777'),
+ '"1645865777" must not be a valid Polish VAT identification number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::nip()->assert('1645865778'),
+ '- "1645865778" must be a valid Polish VAT identification number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::nip())->assert('1645865777'),
+ '- "1645865777" must not be a valid Polish VAT identification number',
+));
diff --git a/tests/feature/Rules/NoTest.php b/tests/feature/Rules/NoTest.php
new file mode 100644
index 00000000..6d627ed8
--- /dev/null
+++ b/tests/feature/Rules/NoTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::not(v::no())->assert('No'),
+ '"No" must not be similar to "No"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::no()->assert('Yes'),
+ '"Yes" must be similar to "No"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::not(v::no())->assert('No'),
+ '- "No" must not be similar to "No"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::no()->assert('Yes'),
+ '- "Yes" must be similar to "No"',
+));
diff --git a/tests/feature/Rules/NoWhitespaceTest.php b/tests/feature/Rules/NoWhitespaceTest.php
new file mode 100644
index 00000000..1b9957c3
--- /dev/null
+++ b/tests/feature/Rules/NoWhitespaceTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::noWhitespace()->assert('w poiur'),
+ '"w poiur" must not contain whitespaces',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::noWhitespace())->assert('wpoiur'),
+ '"wpoiur" must contain at least one whitespace',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::noWhitespace()->assert('w poiur'),
+ '- "w poiur" must not contain whitespaces',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::noWhitespace())->assert('wpoiur'),
+ '- "wpoiur" must contain at least one whitespace',
+));
diff --git a/tests/feature/Rules/NoneOfTest.php b/tests/feature/Rules/NoneOfTest.php
new file mode 100644
index 00000000..ca5a15a8
--- /dev/null
+++ b/tests/feature/Rules/NoneOfTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::noneOf(v::intType(), v::positive())->assert(42),
+ '42 must not be an integer',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::noneOf(v::intType(), v::positive()))->assert('-1'),
+ '"-1" must be an integer',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::noneOf(v::intType(), v::positive())->assert(42),
+ <<<'FULL_MESSAGE'
+ - None of these rules must pass for 42
+ - 42 must not be an integer
+ - 42 must not be a positive number
+ FULL_MESSAGE,
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::noneOf(v::intType(), v::positive()))->assert('-1'),
+ <<<'FULL_MESSAGE'
+- All of these rules must pass for "-1"
+ - "-1" must be an integer
+ - "-1" must be a positive number
+FULL_MESSAGE,
+));
diff --git a/tests/feature/Rules/NotBlankTest.php b/tests/feature/Rules/NotBlankTest.php
new file mode 100644
index 00000000..0edac0bb
--- /dev/null
+++ b/tests/feature/Rules/NotBlankTest.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::notBlank()->assert(null),
+ 'The value must not be blank',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::notBlank()->setName('Field')->assert(null),
+ 'Field must not be blank',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::notBlank())->assert(1),
+ '1 must be blank',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::notBlank()->assert(''),
+ '- The value must not be blank',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::notBlank()->setName('Field')->assert(''),
+ '- Field must not be blank',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::notBlank())->assert([1]),
+ '- `[1]` must be blank',
+));
diff --git a/tests/feature/Rules/NotEmojiTest.php b/tests/feature/Rules/NotEmojiTest.php
new file mode 100644
index 00000000..9aa0260a
--- /dev/null
+++ b/tests/feature/Rules/NotEmojiTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::notEmoji()->assert('🍕'),
+ '"🍕" must not contain an emoji',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::notEmoji())->assert('AB'),
+ '"AB" must contain an emoji',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::notEmoji()->assert('🏄'),
+ '- "🏄" must not contain an emoji',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::notEmoji())->assert('YZ'),
+ '- "YZ" must contain an emoji',
+));
diff --git a/tests/feature/Rules/NotEmptyTest.php b/tests/feature/Rules/NotEmptyTest.php
new file mode 100644
index 00000000..6b974986
--- /dev/null
+++ b/tests/feature/Rules/NotEmptyTest.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::notEmpty()->assert(null),
+ 'The value must not be empty',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::notEmpty()->setName('Field')->assert(null),
+ 'Field must not be empty',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::notEmpty())->assert(1),
+ '1 must be empty',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::notEmpty()->assert(''),
+ '- The value must not be empty',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::notEmpty()->setName('Field')->assert(''),
+ '- Field must not be empty',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::notEmpty())->assert([1]),
+ '- `[1]` must be empty',
+));
diff --git a/tests/feature/Rules/NotUndefTest.php b/tests/feature/Rules/NotUndefTest.php
new file mode 100644
index 00000000..8d83bc07
--- /dev/null
+++ b/tests/feature/Rules/NotUndefTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::notUndef()->assert(null),
+ 'The value must be defined',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::notUndef())->assert(0),
+ 'The value must be undefined',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::notUndef()->setName('Field')->assert(null),
+ 'Field must be defined',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::notUndef()->setName('Field'))->assert([]),
+ 'Field must be undefined',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::notUndef()->assert(''),
+ '- The value must be defined',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::notUndef())->assert([]),
+ '- The value must be undefined',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::notUndef()->setName('Field')->assert(''),
+ '- Field must be defined',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::notUndef()->setName('Field'))->assert([]),
+ '- Field must be undefined',
+));
diff --git a/tests/feature/Rules/NullOrTest.php b/tests/feature/Rules/NullOrTest.php
new file mode 100644
index 00000000..ea4984f9
--- /dev/null
+++ b/tests/feature/Rules/NullOrTest.php
@@ -0,0 +1,111 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::nullOr(v::alpha())->assert(1234),
+ '1234 must contain only letters (a-z) or must be null',
+ '- 1234 must contain only letters (a-z) or must be null',
+ ['nullOrAlpha' => '1234 must contain only letters (a-z) or must be null']
+));
+
+test('Inverted wrapper', expectAll(
+ fn() => v::not(v::nullOr(v::alpha()))->assert('alpha'),
+ '"alpha" must not contain letters (a-z) and must not be null',
+ '- "alpha" must not contain letters (a-z) and must not be null',
+ ['notNullOrAlpha' => '"alpha" must not contain letters (a-z) and must not be null']
+));
+
+test('Inverted wrapped', expectAll(
+ fn() => v::nullOr(v::not(v::alpha()))->assert('alpha'),
+ '"alpha" must not contain letters (a-z) or must be null',
+ '- "alpha" must not contain letters (a-z) or must be null',
+ ['nullOrNotAlpha' => '"alpha" must not contain letters (a-z) or must be null']
+));
+
+test('Inverted nullined', expectAll(
+ fn() => v::not(v::nullOr(v::alpha()))->assert(null),
+ '`null` must not contain letters (a-z) and must not be null',
+ '- `null` must not contain letters (a-z) and must not be null',
+ ['notNullOrAlpha' => '`null` must not contain letters (a-z) and must not be null']
+));
+
+test('Inverted nullined, wrapped name', expectAll(
+ fn() => v::not(v::nullOr(v::alpha()->setName('Wrapped')))->assert(null),
+ 'Wrapped must not contain letters (a-z) and must not be null',
+ '- Wrapped must not contain letters (a-z) and must not be null',
+ ['notNullOrAlpha' => 'Wrapped must not contain letters (a-z) and must not be null']
+));
+
+test('Inverted nullined, wrapper name', expectAll(
+ fn() => v::not(v::nullOr(v::alpha())->setName('Wrapper'))->assert(null),
+ 'Wrapper must not contain letters (a-z) and must not be null',
+ '- Wrapper must not contain letters (a-z) and must not be null',
+ ['notNullOrAlpha' => 'Wrapper must not contain letters (a-z) and must not be null']
+));
+
+test('Inverted nullined, not name', expectAll(
+ fn() => v::not(v::nullOr(v::alpha()))->setName('Not')->assert(null),
+ 'Not must not contain letters (a-z) and must not be null',
+ '- Not must not contain letters (a-z) and must not be null',
+ ['notNullOrAlpha' => 'Not must not contain letters (a-z) and must not be null']
+));
+
+test('With template', expectAll(
+ fn() => v::nullOr(v::alpha())->assert(123, 'Nine nimble numismatists near Naples'),
+ 'Nine nimble numismatists near Naples',
+ '- Nine nimble numismatists near Naples',
+ ['nullOrAlpha' => 'Nine nimble numismatists near Naples']
+));
+
+test('With array template', expectAll(
+ fn() => v::nullOr(v::alpha())->assert(123, ['nullOrAlpha' => 'Next to nifty null notations']),
+ 'Next to nifty null notations',
+ '- Next to nifty null notations',
+ ['nullOrAlpha' => 'Next to nifty null notations']
+));
+
+test('Inverted nullined with template', expectAll(
+ fn() => v::not(v::nullOr(v::alpha()))->assert(null, ['notNullOrAlpha' => 'Next to nifty null notations']),
+ 'Next to nifty null notations',
+ '- Next to nifty null notations',
+ ['notNullOrAlpha' => 'Next to nifty null notations']
+));
+
+test('Without subsequent result', expectAll(
+ fn() => v::nullOr(v::alpha()->stringType())->assert(1234),
+ '1234 must contain only letters (a-z) or must be null',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1234
+ - 1234 must contain only letters (a-z) or must be null
+ - 1234 must be a string or must be null
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1234',
+ 'nullOrAlpha' => '1234 must contain only letters (a-z) or must be null',
+ 'nullOrStringType' => '1234 must be a string or must be null',
+ ]
+));
+
+test('Without subsequent result with templates', expectAll(
+ fn() => v::nullOr(v::alpha()->stringType())->assert(1234, [
+ 'nullOrAlpha' => 'Should be nul or alpha',
+ 'nullOrStringType' => 'Should be nul or string type',
+ ]),
+ 'Should be nul or alpha',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1234
+ - Should be nul or alpha
+ - Should be nul or string type
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1234',
+ 'nullOrAlpha' => 'Should be nul or alpha',
+ 'nullOrStringType' => 'Should be nul or string type',
+ ]
+));
diff --git a/tests/feature/Rules/NullTypeTest.php b/tests/feature/Rules/NullTypeTest.php
new file mode 100644
index 00000000..8eadd439
--- /dev/null
+++ b/tests/feature/Rules/NullTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::nullType()->assert(''),
+ '"" must be null',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::nullType())->assert(null),
+ '`null` must not be null',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::nullType()->assert(false),
+ '- `false` must be null',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::nullType())->assert(null),
+ '- `null` must not be null',
+));
diff --git a/tests/feature/Rules/NumberTest.php b/tests/feature/Rules/NumberTest.php
new file mode 100644
index 00000000..f006b246
--- /dev/null
+++ b/tests/feature/Rules/NumberTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::number()->assert(acos(1.01)),
+ '`NaN` must be a valid number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::number())->assert(42),
+ '42 must not be a number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::number()->assert(NAN),
+ '- `NaN` must be a valid number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::number())->assert(42),
+ '- 42 must not be a number',
+));
diff --git a/tests/feature/Rules/NumericValTest.php b/tests/feature/Rules/NumericValTest.php
new file mode 100644
index 00000000..1a623d88
--- /dev/null
+++ b/tests/feature/Rules/NumericValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::numericVal()->assert('a'),
+ '"a" must be a numeric value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::numericVal())->assert('1'),
+ '"1" must not be a numeric value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::numericVal()->assert('a'),
+ '- "a" must be a numeric value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::numericVal())->assert('1'),
+ '- "1" must not be a numeric value',
+));
diff --git a/tests/feature/Rules/ObjectTypeTest.php b/tests/feature/Rules/ObjectTypeTest.php
new file mode 100644
index 00000000..7dd1ea49
--- /dev/null
+++ b/tests/feature/Rules/ObjectTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::objectType()->assert([]),
+ '`[]` must be an object',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::objectType())->assert(new stdClass()),
+ '`stdClass {}` must not be an object',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::objectType()->assert('test'),
+ '- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be an object',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::objectType())->assert(new ArrayObject()),
+ '- `ArrayObject { getArrayCopy() => [] }` must not be an object',
+));
diff --git a/tests/feature/Rules/OddTest.php b/tests/feature/Rules/OddTest.php
new file mode 100644
index 00000000..9a68befd
--- /dev/null
+++ b/tests/feature/Rules/OddTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::odd()->assert(2),
+ '2 must be an odd number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::odd())->assert(7),
+ '7 must be an even number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::odd()->assert(2),
+ '- 2 must be an odd number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::odd())->assert(9),
+ '- 9 must be an even number',
+));
diff --git a/tests/feature/Rules/PerfectSquareTest.php b/tests/feature/Rules/PerfectSquareTest.php
new file mode 100644
index 00000000..35ba2a96
--- /dev/null
+++ b/tests/feature/Rules/PerfectSquareTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::perfectSquare()->assert(250),
+ '250 must be a perfect square number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::perfectSquare())->assert(9),
+ '9 must not be a perfect square number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::perfectSquare()->assert(7),
+ '- 7 must be a perfect square number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::perfectSquare())->assert(400),
+ '- 400 must not be a perfect square number',
+));
diff --git a/tests/feature/Rules/PeselTest.php b/tests/feature/Rules/PeselTest.php
new file mode 100644
index 00000000..2418d03d
--- /dev/null
+++ b/tests/feature/Rules/PeselTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+require_once 'vendor/autoload.php';
+
+test('Scenario #1', expectMessage(
+ fn() => v::pesel()->assert('21120209251'),
+ '"21120209251" must be a valid PESEL',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::pesel())->assert('21120209256'),
+ '"21120209256" must not be a valid PESEL',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::pesel()->assert('21120209251'),
+ '- "21120209251" must be a valid PESEL',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::pesel())->assert('21120209256'),
+ '- "21120209256" must not be a valid PESEL',
+));
diff --git a/tests/feature/Rules/PhoneTest.php b/tests/feature/Rules/PhoneTest.php
new file mode 100644
index 00000000..32abdc27
--- /dev/null
+++ b/tests/feature/Rules/PhoneTest.php
@@ -0,0 +1,43 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::phone()->assert('123'),
+ '"123" must be a valid telephone number',
+ '- "123" must be a valid telephone number',
+ ['phone' => '"123" must be a valid telephone number']
+));
+
+test('Country-specific', expectAll(
+ fn() => v::phone('BR')->assert('+1 650 253 00 00'),
+ '"+1 650 253 00 00" must be a valid telephone number for country Brazil',
+ '- "+1 650 253 00 00" must be a valid telephone number for country Brazil',
+ ['phone' => '"+1 650 253 00 00" must be a valid telephone number for country Brazil']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::phone())->assert('+55 11 91111 1111'),
+ '"+55 11 91111 1111" must not be a valid telephone number',
+ '- "+55 11 91111 1111" must not be a valid telephone number',
+ ['notPhone' => '"+55 11 91111 1111" must not be a valid telephone number']
+));
+
+test('Default with name', expectAll(
+ fn() => v::phone()->setName('Phone')->assert('123'),
+ 'Phone must be a valid telephone number',
+ '- Phone must be a valid telephone number',
+ ['phone' => 'Phone must be a valid telephone number']
+));
+
+test('Country-specific with name', expectAll(
+ fn() => v::phone('US')->setName('Phone')->assert('123'),
+ 'Phone must be a valid telephone number for country United States',
+ '- Phone must be a valid telephone number for country United States',
+ ['phone' => 'Phone must be a valid telephone number for country United States']
+));
diff --git a/tests/feature/Rules/PhplabelTest.php b/tests/feature/Rules/PhplabelTest.php
new file mode 100644
index 00000000..253e9c02
--- /dev/null
+++ b/tests/feature/Rules/PhplabelTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::phpLabel()->assert('f o o'),
+ '"f o o" must be a valid PHP label',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::phpLabel())->assert('correctOne'),
+ '"correctOne" must not be a valid PHP label',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::phpLabel()->assert('0wner'),
+ '- "0wner" must be a valid PHP label',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::phpLabel())->assert('Respect'),
+ '- "Respect" must not be a valid PHP label',
+));
diff --git a/tests/feature/Rules/PisTest.php b/tests/feature/Rules/PisTest.php
new file mode 100644
index 00000000..13dd014a
--- /dev/null
+++ b/tests/feature/Rules/PisTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::pis()->assert('this thing'),
+ '"this thing" must be a valid PIS number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::pis())->assert('120.6671.406-4'),
+ '"120.6671.406-4" must not be a valid PIS number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::pis()->assert('your mother'),
+ '- "your mother" must be a valid PIS number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::pis())->assert('120.9378.174-5'),
+ '- "120.9378.174-5" must not be a valid PIS number',
+));
diff --git a/tests/feature/Rules/PolishIdCardTest.php b/tests/feature/Rules/PolishIdCardTest.php
new file mode 100644
index 00000000..38a4ed6e
--- /dev/null
+++ b/tests/feature/Rules/PolishIdCardTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+require_once 'vendor/autoload.php';
+
+test('Scenario #1', expectMessage(
+ fn() => v::polishIdCard()->assert('AYE205411'),
+ '"AYE205411" must be a valid Polish Identity Card number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::polishIdCard())->assert('AYE205410'),
+ '"AYE205410" must not be a valid Polish Identity Card number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::polishIdCard()->assert('AYE205411'),
+ '- "AYE205411" must be a valid Polish Identity Card number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::polishIdCard())->assert('AYE205410'),
+ '- "AYE205410" must not be a valid Polish Identity Card number',
+));
diff --git a/tests/feature/Rules/PositiveTest.php b/tests/feature/Rules/PositiveTest.php
new file mode 100644
index 00000000..87edf56c
--- /dev/null
+++ b/tests/feature/Rules/PositiveTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::positive()->assert(-10),
+ '-10 must be a positive number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::positive())->assert(16),
+ '16 must not be a positive number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::positive()->assert('a'),
+ '- "a" must be a positive number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::positive())->assert('165'),
+ '- "165" must not be a positive number',
+));
diff --git a/tests/feature/Rules/PostalCodeTest.php b/tests/feature/Rules/PostalCodeTest.php
new file mode 100644
index 00000000..e3b74844
--- /dev/null
+++ b/tests/feature/Rules/PostalCodeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::postalCode('BR')->assert('1057BV'),
+ '"1057BV" must be a valid postal code on "BR"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::postalCode('NL'))->assert('1057BV'),
+ '"1057BV" must not be a valid postal code on "NL"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::postalCode('BR')->assert('1057BV'),
+ '- "1057BV" must be a valid postal code on "BR"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::postalCode('NL'))->assert('1057BV'),
+ '- "1057BV" must not be a valid postal code on "NL"',
+));
diff --git a/tests/feature/Rules/PrimeNumberTest.php b/tests/feature/Rules/PrimeNumberTest.php
new file mode 100644
index 00000000..46e2390a
--- /dev/null
+++ b/tests/feature/Rules/PrimeNumberTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::primeNumber()->assert(10),
+ '10 must be a prime number',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::primeNumber())->assert(3),
+ '3 must not be a prime number',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::primeNumber()->assert('Foo'),
+ '- "Foo" must be a prime number',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::primeNumber())->assert('+7'),
+ '- "+7" must not be a prime number',
+));
diff --git a/tests/feature/Rules/PrintableTest.php b/tests/feature/Rules/PrintableTest.php
new file mode 100644
index 00000000..1bf9f906
--- /dev/null
+++ b/tests/feature/Rules/PrintableTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::printable()->assert(''),
+ '"" must contain only printable characters',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::printable())->assert('abc'),
+ '"abc" must not contain printable characters',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::printable()->assert('foo' . chr(10) . 'bar'),
+ '- "foo\\nbar" must contain only printable characters',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::printable())->assert('$%asd'),
+ '- "$%asd" must not contain printable characters',
+));
diff --git a/tests/feature/Rules/PropertyExistsTest.php b/tests/feature/Rules/PropertyExistsTest.php
new file mode 100644
index 00000000..bcba8fb5
--- /dev/null
+++ b/tests/feature/Rules/PropertyExistsTest.php
@@ -0,0 +1,36 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default mode', expectAll(
+ fn() => v::propertyExists('foo')->assert((object) ['bar' => 'baz']),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('Inverted mode', expectAll(
+ fn() => v::not(v::propertyExists('foo'))->assert((object) ['foo' => 'baz']),
+ 'foo must not be present',
+ '- foo must not be present',
+ ['foo' => 'foo must not be present']
+));
+
+test('Custom name', expectAll(
+ fn() => v::propertyExists('foo')->setName('Custom name')->assert((object) ['bar' => 'baz']),
+ 'Custom name must be present',
+ '- Custom name must be present',
+ ['foo' => 'Custom name must be present']
+));
+
+test('Custom template', expectAll(
+ fn() => v::propertyExists('foo')->assert((object) ['bar' => 'baz'], 'Custom template for `{{name}}`'),
+ 'Custom template for `foo`',
+ '- Custom template for `foo`',
+ ['foo' => 'Custom template for `foo`']
+));
diff --git a/tests/feature/Rules/PropertyOptionalTest.php b/tests/feature/Rules/PropertyOptionalTest.php
new file mode 100644
index 00000000..e984b154
--- /dev/null
+++ b/tests/feature/Rules/PropertyOptionalTest.php
@@ -0,0 +1,83 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::propertyOptional('foo', v::intType())->assert((object) ['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::propertyOptional('foo', v::intType()))->assert((object) ['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('Inverted with missing property', expectAll(
+ fn() => v::not(v::propertyOptional('foo', v::intType()))->assert(new stdClass()),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('With wrapped name, default', expectAll(
+ fn() => v::propertyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')->assert((object) ['foo' => 'string']),
+ 'Wrapped must be an integer',
+ '- Wrapped must be an integer',
+ ['foo' => 'Wrapped must be an integer']
+));
+
+test('With wrapped name, inverted', expectAll(
+ fn() => v::not(v::propertyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not')->assert((object) ['foo' => 12]),
+ 'Wrapped must not be an integer',
+ '- Wrapped must not be an integer',
+ ['foo' => 'Wrapped must not be an integer']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::propertyOptional('foo', v::intType())
+ ->setName('Wrapper')
+ ->assert((object) ['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::propertyOptional('foo', v::intType())->setName('Wrapper'))
+ ->setName('Not')->assert((object) ['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With "Not" name, inverted', expectAll(
+ fn() => v::not(v::propertyOptional('foo', v::intType()))->setName('Not')->assert((object) ['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With template, default', expectAll(
+ fn() => v::propertyOptional('foo', v::intType())
+ ->assert((object) ['foo' => 'string'], 'Proper property planners plan precise property plots'),
+ 'Proper property planners plan precise property plots',
+ '- Proper property planners plan precise property plots',
+ ['foo' => 'Proper property planners plan precise property plots']
+));
+
+test('With template, inverted', expectAll(
+ fn() => v::not(v::propertyOptional('foo', v::intType()))
+ ->assert((object) ['foo' => 12], 'Not proving prudent property planning promotes prosperity'),
+ 'Not proving prudent property planning promotes prosperity',
+ '- Not proving prudent property planning promotes prosperity',
+ ['foo' => 'Not proving prudent property planning promotes prosperity']
+));
diff --git a/tests/feature/Rules/PropertyTest.php b/tests/feature/Rules/PropertyTest.php
new file mode 100644
index 00000000..0833cf71
--- /dev/null
+++ b/tests/feature/Rules/PropertyTest.php
@@ -0,0 +1,106 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Missing property', expectAll(
+ fn() => v::property('foo', v::intType())->assert(new stdClass()),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('Default', expectAll(
+ fn() => v::property('foo', v::intType())->assert((object) ['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('Inverted', expectAll(
+ fn() => v::not(v::property('foo', v::intType()))->assert((object) ['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('Double-inverted with missing property', expectAll(
+ fn() => v::not(v::not(v::property('foo', v::intType())))->assert(new stdClass()),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('With wrapped name, missing property', expectAll(
+ fn() => v::property('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')->assert(new stdClass()),
+ 'Wrapped must be present',
+ '- Wrapped must be present',
+ ['foo' => 'Wrapped must be present']
+));
+
+test('With wrapped name, default', expectAll(
+ fn() => v::property('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')->assert((object) ['foo' => 'string']),
+ 'Wrapped must be an integer',
+ '- Wrapped must be an integer',
+ ['foo' => 'Wrapped must be an integer']
+));
+
+test('With wrapped name, inverted', expectAll(
+ fn() => v::not(
+ v::property('foo', v::intType()->setName('Wrapped'))->setName('Wrapper')
+ )
+ ->setName('Not')
+ ->assert((object) ['foo' => 12]),
+ 'Wrapped must not be an integer',
+ '- Wrapped must not be an integer',
+ ['foo' => 'Wrapped must not be an integer']
+));
+
+test('With wrapper name, default', expectAll(
+ fn() => v::property('foo', v::intType())->setName('Wrapper')->assert((object) ['foo' => 'string']),
+ 'foo must be an integer',
+ '- foo must be an integer',
+ ['foo' => 'foo must be an integer']
+));
+
+test('With wrapper name, missing property', expectAll(
+ fn() => v::property('foo', v::intType())->setName('Wrapper')->assert(new stdClass()),
+ 'foo must be present',
+ '- foo must be present',
+ ['foo' => 'foo must be present']
+));
+
+test('With wrapper name, inverted', expectAll(
+ fn() => v::not(v::property('foo', v::intType())->setName('Wrapper'))->setName('Not')
+ ->assert((object) ['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With "Not" name, inverted', expectAll(
+ fn() => v::not(v::property('foo', v::intType()))->setName('Not')->assert((object) ['foo' => 12]),
+ 'foo must not be an integer',
+ '- foo must not be an integer',
+ ['foo' => 'foo must not be an integer']
+));
+
+test('With template, default', expectAll(
+ fn() => v::property('foo', v::intType())
+ ->assert((object) ['foo' => 'string'], 'Particularly precautions perplexing property'),
+ 'Particularly precautions perplexing property',
+ '- Particularly precautions perplexing property',
+ ['foo' => 'Particularly precautions perplexing property']
+));
+
+test('With template, inverted', expectAll(
+ fn() => v::not(v::property('foo', v::intType()))
+ ->assert((object) ['foo' => 12], 'Not a prompt prospect of a particularly primitive property'),
+ 'Not a prompt prospect of a particularly primitive property',
+ '- Not a prompt prospect of a particularly primitive property',
+ ['foo' => 'Not a prompt prospect of a particularly primitive property']
+));
diff --git a/tests/feature/Rules/PunctTest.php b/tests/feature/Rules/PunctTest.php
new file mode 100644
index 00000000..76d03900
--- /dev/null
+++ b/tests/feature/Rules/PunctTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::punct()->assert('a'),
+ '"a" must contain only punctuation characters',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::punct('c')->assert('b'),
+ '"b" must contain only punctuation characters and "c"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::punct())->assert('.'),
+ '"." must not contain punctuation characters',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::punct('d'))->assert('?'),
+ '"?" must not contain punctuation characters or "d"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::punct()->assert('e'),
+ '- "e" must contain only punctuation characters',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::punct('f')->assert('g'),
+ '- "g" must contain only punctuation characters and "f"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::punct())->assert('!'),
+ '- "!" must not contain punctuation characters',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::punct('h'))->assert(';'),
+ '- ";" must not contain punctuation characters or "h"',
+));
diff --git a/tests/feature/Rules/ReadableTest.php b/tests/feature/Rules/ReadableTest.php
new file mode 100644
index 00000000..5f1cfd41
--- /dev/null
+++ b/tests/feature/Rules/ReadableTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::readable()->assert('tests/fixtures/invalid-image.jpg'),
+ '"tests/fixtures/invalid-image.jpg" must be readable',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::readable())->assert('tests/fixtures/valid-image.png'),
+ '"tests/fixtures/valid-image.png" must not be readable',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::readable()->assert(new stdClass()),
+ '- `stdClass {}` must be readable',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::readable())->assert('tests/fixtures/valid-image.png'),
+ '- "tests/fixtures/valid-image.png" must not be readable',
+));
diff --git a/tests/feature/Rules/RegexTest.php b/tests/feature/Rules/RegexTest.php
new file mode 100644
index 00000000..00c3dc02
--- /dev/null
+++ b/tests/feature/Rules/RegexTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::regex('/^w+$/')->assert('w poiur'),
+ '"w poiur" must match the pattern `/^w+$/`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::regex('/^[a-z]+$/'))->assert('wpoiur'),
+ '"wpoiur" must not match the pattern `/^[a-z]+$/`',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::regex('/^w+$/')->assert(new stdClass()),
+ '- `stdClass {}` must match the pattern `/^w+$/`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::regex('/^[a-z]+$/i'))->assert('wPoiur'),
+ '- "wPoiur" must not match the pattern `/^[a-z]+$/i`',
+));
diff --git a/tests/feature/Rules/ResourceTypeTest.php b/tests/feature/Rules/ResourceTypeTest.php
new file mode 100644
index 00000000..5057a24d
--- /dev/null
+++ b/tests/feature/Rules/ResourceTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::resourceType()->assert('test'),
+ '`test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be a resource',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::resourceType())->assert(tmpfile()),
+ '`resource ` must not be a resource',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::resourceType()->assert([]),
+ '- `[]` must be a resource',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::resourceType())->assert(tmpfile()),
+ '- `resource ` must not be a resource',
+));
diff --git a/tests/feature/Rules/RomanTest.php b/tests/feature/Rules/RomanTest.php
new file mode 100644
index 00000000..958c15ee
--- /dev/null
+++ b/tests/feature/Rules/RomanTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::roman()->assert(1234),
+ '1234 must be a valid Roman numeral',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::roman())->assert('XL'),
+ '"XL" must not be a valid Roman numeral',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::roman()->assert('e2'),
+ '- "e2" must be a valid Roman numeral',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::roman())->assert('IV'),
+ '- "IV" must not be a valid Roman numeral',
+));
diff --git a/tests/feature/Rules/ScalarValTest.php b/tests/feature/Rules/ScalarValTest.php
new file mode 100644
index 00000000..de03bf05
--- /dev/null
+++ b/tests/feature/Rules/ScalarValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::scalarVal()->assert([]),
+ '`[]` must be a scalar value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::scalarVal())->assert(true),
+ '`true` must not be a scalar value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::scalarVal()->assert(new stdClass()),
+ '- `stdClass {}` must be a scalar value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::scalarVal())->assert(42),
+ '- 42 must not be a scalar value',
+));
diff --git a/tests/feature/Rules/SizeTest.php b/tests/feature/Rules/SizeTest.php
new file mode 100644
index 00000000..89aa1ca8
--- /dev/null
+++ b/tests/feature/Rules/SizeTest.php
@@ -0,0 +1,68 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::size('1kb', '2kb')->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must be between "1kb" and "2kb"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::size('700kb', null)->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must be greater than "700kb"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::size(null, '1kb')->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must be lower than "1kb"',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::size('500kb', '600kb'))->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must not be between "500kb" and "600kb"',
+));
+
+test('Scenario #5', expectMessage(
+ fn() => v::not(v::size('500kb', null))->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must not be greater than "500kb"',
+));
+
+test('Scenario #6', expectMessage(
+ fn() => v::not(v::size(null, '600kb'))->assert('tests/fixtures/valid-image.gif'),
+ '"tests/fixtures/valid-image.gif" must not be lower than "600kb"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::size('1kb', '2kb')->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must be between "1kb" and "2kb"',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::size('700kb', null)->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must be greater than "700kb"',
+));
+
+test('Scenario #9', expectFullMessage(
+ fn() => v::size(null, '1kb')->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must be lower than "1kb"',
+));
+
+test('Scenario #10', expectFullMessage(
+ fn() => v::not(v::size('500kb', '600kb'))->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must not be between "500kb" and "600kb"',
+));
+
+test('Scenario #11', expectFullMessage(
+ fn() => v::not(v::size('500kb', null))->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must not be greater than "500kb"',
+));
+
+test('Scenario #12', expectFullMessage(
+ fn() => v::not(v::size(null, '600kb'))->assert('tests/fixtures/valid-image.gif'),
+ '- "tests/fixtures/valid-image.gif" must not be lower than "600kb"',
+));
diff --git a/tests/feature/Rules/SlugTest.php b/tests/feature/Rules/SlugTest.php
new file mode 100644
index 00000000..f474a97c
--- /dev/null
+++ b/tests/feature/Rules/SlugTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::slug()->assert('my-Slug'),
+ '"my-Slug" must be a valid slug',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::slug())->assert('my-slug'),
+ '"my-slug" must not be a valid slug',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::slug()->assert('my-Slug'),
+ '- "my-Slug" must be a valid slug',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::slug())->assert('my-slug'),
+ '- "my-slug" must not be a valid slug',
+));
diff --git a/tests/feature/Rules/SortedTest.php b/tests/feature/Rules/SortedTest.php
new file mode 100644
index 00000000..99f14212
--- /dev/null
+++ b/tests/feature/Rules/SortedTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::sorted('ASC')->assert([1, 3, 2]),
+ '`[1, 3, 2]` must be sorted in ascending order',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::sorted('DESC')->assert([1, 2, 3]),
+ '`[1, 2, 3]` must be sorted in descending order',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::sorted('ASC'))->assert([1, 2, 3]),
+ '`[1, 2, 3]` must not be sorted in ascending order',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::sorted('DESC'))->assert([3, 2, 1]),
+ '`[3, 2, 1]` must not be sorted in descending order',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::sorted('ASC')->assert([3, 2, 1]),
+ '- `[3, 2, 1]` must be sorted in ascending order',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::sorted('DESC')->assert([1, 2, 3]),
+ '- `[1, 2, 3]` must be sorted in descending order',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::sorted('ASC'))->assert([1, 2, 3]),
+ '- `[1, 2, 3]` must not be sorted in ascending order',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::sorted('DESC'))->assert([3, 2, 1]),
+ '- `[3, 2, 1]` must not be sorted in descending order',
+));
diff --git a/tests/feature/Rules/SpaceTest.php b/tests/feature/Rules/SpaceTest.php
new file mode 100644
index 00000000..f179a306
--- /dev/null
+++ b/tests/feature/Rules/SpaceTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::space()->assert('ab'),
+ '"ab" must contain only space characters',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::space('c')->assert('cd'),
+ '"cd" must contain only space characters and "c"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::space())->assert("\t"),
+ '"\\t" must not contain space characters',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::space('def'))->assert("\r"),
+ '"\\r" must not contain space characters or "def"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::space()->assert('ef'),
+ '- "ef" must contain only space characters',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::space('e')->assert('gh'),
+ '- "gh" must contain only space characters and "e"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::space())->assert("\n"),
+ '- "\\n" must not contain space characters',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::space('yk'))->assert(' k'),
+ '- " k" must not contain space characters or "yk"',
+));
diff --git a/tests/feature/Rules/StartsWithTest.php b/tests/feature/Rules/StartsWithTest.php
new file mode 100644
index 00000000..91139658
--- /dev/null
+++ b/tests/feature/Rules/StartsWithTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::startsWith('b')->assert(['a', 'b']),
+ '`["a", "b"]` must start with "b"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::startsWith(1.1))->assert([1.1, 2.2]),
+ '`[1.1, 2.2]` must not start with 1.1',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::startsWith('3.3', true)->assert([3.3, 4.4]),
+ '- `[3.3, 4.4]` must start with "3.3"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::startsWith('c'))->assert(['c', 'd']),
+ '- `["c", "d"]` must not start with "c"',
+));
diff --git a/tests/feature/Rules/StringTypeTest.php b/tests/feature/Rules/StringTypeTest.php
new file mode 100644
index 00000000..d1a30b87
--- /dev/null
+++ b/tests/feature/Rules/StringTypeTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::stringType()->assert(42),
+ '42 must be a string',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::stringType())->assert('foo'),
+ '"foo" must not be a string',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::stringType()->assert(true),
+ '- `true` must be a string',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::stringType())->assert('bar'),
+ '- "bar" must not be a string',
+));
diff --git a/tests/feature/Rules/StringValTest.php b/tests/feature/Rules/StringValTest.php
new file mode 100644
index 00000000..e2b04837
--- /dev/null
+++ b/tests/feature/Rules/StringValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::stringVal()->assert([]),
+ '`[]` must be a string value',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::stringVal())->assert(true),
+ '`true` must not be a string value',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::stringVal()->assert(new stdClass()),
+ '- `stdClass {}` must be a string value',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::stringVal())->assert(42),
+ '- 42 must not be a string value',
+));
diff --git a/tests/feature/Rules/SubsetTest.php b/tests/feature/Rules/SubsetTest.php
new file mode 100644
index 00000000..370fb03f
--- /dev/null
+++ b/tests/feature/Rules/SubsetTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::subset([1, 2])->assert([1, 2, 3]),
+ '`[1, 2, 3]` must be subset of `[1, 2]`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::subset([1, 2, 3]))->assert([1, 2]),
+ '`[1, 2]` must not be subset of `[1, 2, 3]`',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::subset(['A', 'B'])->assert(['B', 'C']),
+ '- `["B", "C"]` must be subset of `["A", "B"]`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::subset(['A']))->assert(['A']),
+ '- `["A"]` must not be subset of `["A"]`',
+));
diff --git a/tests/feature/Rules/SymbolicLinkTest.php b/tests/feature/Rules/SymbolicLinkTest.php
new file mode 100644
index 00000000..68ada31d
--- /dev/null
+++ b/tests/feature/Rules/SymbolicLinkTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::symbolicLink()->assert('tests/fixtures/fake-filename'),
+ '"tests/fixtures/fake-filename" must be a symbolic link',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::symbolicLink())->assert('tests/fixtures/symbolic-link'),
+ '"tests/fixtures/symbolic-link" must not be a symbolic link',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::symbolicLink()->assert('tests/fixtures/fake-filename'),
+ '- "tests/fixtures/fake-filename" must be a symbolic link',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::symbolicLink())->assert('tests/fixtures/symbolic-link'),
+ '- "tests/fixtures/symbolic-link" must not be a symbolic link',
+));
diff --git a/tests/feature/Rules/TimeTest.php b/tests/feature/Rules/TimeTest.php
new file mode 100644
index 00000000..3fdf4a73
--- /dev/null
+++ b/tests/feature/Rules/TimeTest.php
@@ -0,0 +1,30 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+test('Scenario #1', expectMessage(
+ fn() => v::time()->assert('2018-01-30'),
+ '"2018-01-30" must be a valid time in the format "23:59:59"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::time())->assert('09:25:46'),
+ '"09:25:46" must not be a valid time in the format "23:59:59"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::time()->assert('2018-01-30'),
+ '- "2018-01-30" must be a valid time in the format "23:59:59"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::time('g:i A'))->assert('8:13 AM'),
+ '- "8:13 AM" must not be a valid time in the format "11:59 PM"',
+));
diff --git a/tests/feature/Rules/TldTest.php b/tests/feature/Rules/TldTest.php
new file mode 100644
index 00000000..a2fc7237
--- /dev/null
+++ b/tests/feature/Rules/TldTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::tld()->assert('42'),
+ '"42" must be a valid top-level domain name',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::tld())->assert('com'),
+ '"com" must not be a valid top-level domain name',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::tld()->assert('1984'),
+ '- "1984" must be a valid top-level domain name',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::tld())->assert('com'),
+ '- "com" must not be a valid top-level domain name',
+));
diff --git a/tests/feature/Rules/TrueValTest.php b/tests/feature/Rules/TrueValTest.php
new file mode 100644
index 00000000..561f5056
--- /dev/null
+++ b/tests/feature/Rules/TrueValTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::trueVal()->assert(false),
+ '`false` must evaluate to `true`',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::trueVal())->assert(1),
+ '1 must not evaluate to `true`',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::trueVal()->assert(0),
+ '- 0 must evaluate to `true`',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::trueVal())->assert('true'),
+ '- "true" must not evaluate to `true`',
+));
diff --git a/tests/feature/Rules/UndefOrTest.php b/tests/feature/Rules/UndefOrTest.php
new file mode 100644
index 00000000..921bf962
--- /dev/null
+++ b/tests/feature/Rules/UndefOrTest.php
@@ -0,0 +1,111 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Default', expectAll(
+ fn() => v::undefOr(v::alpha())->assert(1234),
+ '1234 must contain only letters (a-z) or must be undefined',
+ '- 1234 must contain only letters (a-z) or must be undefined',
+ ['undefOrAlpha' => '1234 must contain only letters (a-z) or must be undefined']
+));
+
+test('Inverted wrapper', expectAll(
+ fn() => v::not(v::undefOr(v::alpha()))->assert('alpha'),
+ '"alpha" must not contain letters (a-z) and must not be undefined',
+ '- "alpha" must not contain letters (a-z) and must not be undefined',
+ ['notUndefOrAlpha' => '"alpha" must not contain letters (a-z) and must not be undefined']
+));
+
+test('Inverted wrapped', expectAll(
+ fn() => v::undefOr(v::not(v::alpha()))->assert('alpha'),
+ '"alpha" must not contain letters (a-z) or must be undefined',
+ '- "alpha" must not contain letters (a-z) or must be undefined',
+ ['undefOrNotAlpha' => '"alpha" must not contain letters (a-z) or must be undefined']
+));
+
+test('Inverted undefined', expectAll(
+ fn() => v::not(v::undefOr(v::alpha()))->assert(null),
+ '`null` must not contain letters (a-z) and must not be undefined',
+ '- `null` must not contain letters (a-z) and must not be undefined',
+ ['notUndefOrAlpha' => '`null` must not contain letters (a-z) and must not be undefined']
+));
+
+test('Inverted undefined, wrapped name', expectAll(
+ fn() => v::not(v::undefOr(v::alpha()->setName('Wrapped')))->assert(null),
+ 'Wrapped must not contain letters (a-z) and must not be undefined',
+ '- Wrapped must not contain letters (a-z) and must not be undefined',
+ ['notUndefOrAlpha' => 'Wrapped must not contain letters (a-z) and must not be undefined']
+));
+
+test('Inverted undefined, wrapper name', expectAll(
+ fn() => v::not(v::undefOr(v::alpha())->setName('Wrapper'))->assert(null),
+ 'Wrapper must not contain letters (a-z) and must not be undefined',
+ '- Wrapper must not contain letters (a-z) and must not be undefined',
+ ['notUndefOrAlpha' => 'Wrapper must not contain letters (a-z) and must not be undefined']
+));
+
+test('Inverted undefined, not name', expectAll(
+ fn() => v::not(v::undefOr(v::alpha()))->setName('Not')->assert(null),
+ 'Not must not contain letters (a-z) and must not be undefined',
+ '- Not must not contain letters (a-z) and must not be undefined',
+ ['notUndefOrAlpha' => 'Not must not contain letters (a-z) and must not be undefined']
+));
+
+test('With template', expectAll(
+ fn() => v::undefOr(v::alpha())->assert(123, 'Underneath the undulating umbrella'),
+ 'Underneath the undulating umbrella',
+ '- Underneath the undulating umbrella',
+ ['undefOrAlpha' => 'Underneath the undulating umbrella']
+));
+
+test('With array template', expectAll(
+ fn() => v::undefOr(v::alpha())->assert(123, ['undefOrAlpha' => 'Undefined number of unique unicorns']),
+ 'Undefined number of unique unicorns',
+ '- Undefined number of unique unicorns',
+ ['undefOrAlpha' => 'Undefined number of unique unicorns']
+));
+
+test('Inverted undefined with template', expectAll(
+ fn() => v::not(v::undefOr(v::alpha()))->assert('', ['notUndefOrAlpha' => 'Should not be undefined or alpha']),
+ 'Should not be undefined or alpha',
+ '- Should not be undefined or alpha',
+ ['notUndefOrAlpha' => 'Should not be undefined or alpha']
+));
+
+test('Without subsequent result', expectAll(
+ fn() => v::undefOr(v::alpha()->stringType())->assert(1234),
+ '1234 must contain only letters (a-z) or must be undefined',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1234
+ - 1234 must contain only letters (a-z) or must be undefined
+ - 1234 must be a string or must be undefined
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1234',
+ 'undefOrAlpha' => '1234 must contain only letters (a-z) or must be undefined',
+ 'undefOrStringType' => '1234 must be a string or must be undefined',
+ ]
+));
+
+test('Without subsequent result with templates', expectAll(
+ fn() => v::undefOr(v::alpha()->stringType())->assert(1234, [
+ 'undefOrAlpha' => 'Should be nul or alpha',
+ 'undefOrStringType' => 'Should be nul or string type',
+ ]),
+ 'Should be nul or alpha',
+ <<<'FULL_MESSAGE'
+ - All of the required rules must pass for 1234
+ - Should be nul or alpha
+ - Should be nul or string type
+ FULL_MESSAGE,
+ [
+ '__root__' => 'All of the required rules must pass for 1234',
+ 'undefOrAlpha' => 'Should be nul or alpha',
+ 'undefOrStringType' => 'Should be nul or string type',
+ ]
+));
diff --git a/tests/feature/Rules/UniqueTest.php b/tests/feature/Rules/UniqueTest.php
new file mode 100644
index 00000000..6b43ea1c
--- /dev/null
+++ b/tests/feature/Rules/UniqueTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::unique()->assert([1, 2, 2, 3]),
+ '`[1, 2, 2, 3]` must not contain duplicates',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::unique())->assert([1, 2, 3, 4]),
+ '`[1, 2, 3, 4]` must contain duplicates',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::unique()->assert('test'),
+ '- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must not contain duplicates',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::unique())->assert(['a', 'b', 'c']),
+ '- `["a", "b", "c"]` must contain duplicates',
+));
diff --git a/tests/feature/Rules/UploadedTest.php b/tests/feature/Rules/UploadedTest.php
new file mode 100644
index 00000000..008c0e11
--- /dev/null
+++ b/tests/feature/Rules/UploadedTest.php
@@ -0,0 +1,40 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ function (): void {
+ uopz_set_return('is_uploaded_file', false);
+ v::uploaded()->assert('filename');
+ },
+ '"filename" must be an uploaded file',
+))->skip(extension_loaded('uopz') == false, 'Extension "uopz" is required to test "Uploaded" rule');
+
+test('Scenario #2', expectMessage(
+ function (): void {
+ uopz_set_return('is_uploaded_file', true);
+ v::not(v::uploaded())->assert('filename');
+ },
+ '"filename" must not be an uploaded file',
+))->skip(extension_loaded('uopz') == false, 'Extension "uopz" is required to test "Uploaded" rule');
+
+test('Scenario #3', expectFullMessage(
+ function (): void {
+ uopz_set_return('is_uploaded_file', false);
+ v::uploaded()->assert('filename');
+ },
+ '- "filename" must be an uploaded file',
+))->skip(extension_loaded('uopz') == false, 'Extension "uopz" is required to test "Uploaded" rule');
+
+test('Scenario #4', expectFullMessage(
+ function (): void {
+ uopz_set_return('is_uploaded_file', true);
+ v::not(v::uploaded())->assert('filename');
+ },
+ '- "filename" must not be an uploaded file',
+))->skip(extension_loaded('uopz') == false, 'Extension "uopz" is required to test "Uploaded" rule');
diff --git a/tests/feature/Rules/UppercaseTest.php b/tests/feature/Rules/UppercaseTest.php
new file mode 100644
index 00000000..5e101e88
--- /dev/null
+++ b/tests/feature/Rules/UppercaseTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::uppercase()->assert('lowercase'),
+ '"lowercase" must contain only uppercase letters',
+));
+
+test('Scenario #2', expectFullMessage(
+ fn() => v::uppercase()->assert('lowercase'),
+ '- "lowercase" must contain only uppercase letters',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::uppercase())->assert('UPPERCASE'),
+ '"UPPERCASE" must not contain only uppercase letters',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::uppercase())->assert('UPPERCASE'),
+ '- "UPPERCASE" must not contain only uppercase letters',
+));
diff --git a/tests/feature/Rules/UrlTest.php b/tests/feature/Rules/UrlTest.php
new file mode 100644
index 00000000..7a9a3271
--- /dev/null
+++ b/tests/feature/Rules/UrlTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::url()->assert('example.com'),
+ '"example.com" must be a URL',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::url())->assert('http://example.com'),
+ '"http://example.com" must not be a URL',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::url()->assert('example.com'),
+ '- "example.com" must be a URL',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::url())->assert('http://example.com'),
+ '- "http://example.com" must not be a URL',
+));
diff --git a/tests/feature/Rules/UuidTest.php b/tests/feature/Rules/UuidTest.php
new file mode 100644
index 00000000..7f5241c7
--- /dev/null
+++ b/tests/feature/Rules/UuidTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::uuid()->assert('g71a18f4-3a13-11e7-a919-92ebcb67fe33'),
+ '"g71a18f4-3a13-11e7-a919-92ebcb67fe33" must be a valid UUID',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::uuid(1)->assert('e0b5ffb9-9caf-2a34-9673-8fc91db78be6'),
+ '"e0b5ffb9-9caf-2a34-9673-8fc91db78be6" must be a valid UUID version 1',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::uuid())->assert('fb3a7909-8034-59f5-8f38-21adbc168db7'),
+ '"fb3a7909-8034-59f5-8f38-21adbc168db7" must not be a valid UUID',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::uuid(3))->assert('11a38b9a-b3da-360f-9353-a5a725514269'),
+ '"11a38b9a-b3da-360f-9353-a5a725514269" must not be a valid UUID version 3',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::uuid()->assert('g71a18f4-3a13-11e7-a919-92ebcb67fe33'),
+ '- "g71a18f4-3a13-11e7-a919-92ebcb67fe33" must be a valid UUID',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::uuid(4)->assert('a71a18f4-3a13-11e7-a919-92ebcb67fe33'),
+ '- "a71a18f4-3a13-11e7-a919-92ebcb67fe33" must be a valid UUID version 4',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::uuid())->assert('e0b5ffb9-9caf-4a34-9673-8fc91db78be6'),
+ '- "e0b5ffb9-9caf-4a34-9673-8fc91db78be6" must not be a valid UUID',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::uuid(5))->assert('c4a760a8-dbcf-5254-a0d9-6a4474bd1b62'),
+ '- "c4a760a8-dbcf-5254-a0d9-6a4474bd1b62" must not be a valid UUID version 5',
+));
diff --git a/tests/feature/Rules/VersionTest.php b/tests/feature/Rules/VersionTest.php
new file mode 100644
index 00000000..83953ab6
--- /dev/null
+++ b/tests/feature/Rules/VersionTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::version()->assert('1.3.7--'),
+ '"1.3.7--" must be a version',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::version())->assert('1.0.0-alpha'),
+ '"1.0.0-alpha" must not be a version',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::version()->assert('1.2.3.4-beta'),
+ '- "1.2.3.4-beta" must be a version',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::version())->assert('1.3.7-rc.1'),
+ '- "1.3.7-rc.1" must not be a version',
+));
diff --git a/tests/feature/Rules/VideoUrlTest.php b/tests/feature/Rules/VideoUrlTest.php
new file mode 100644
index 00000000..6b9989d6
--- /dev/null
+++ b/tests/feature/Rules/VideoUrlTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::videoUrl()->assert('example.com'),
+ '"example.com" must be a valid video URL',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::videoUrl('YouTube')->assert('example.com'),
+ '"example.com" must be a valid YouTube video URL',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::videoUrl())->assert('https://player.vimeo.com/video/7178746722'),
+ '"https://player.vimeo.com/video/7178746722" must not be a valid video URL',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::videoUrl('YouTube'))->assert('https://www.youtube.com/embed/netHLn9TScY'),
+ '"https://www.youtube.com/embed/netHLn9TScY" must not be a valid YouTube video URL',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::videoUrl()->assert('example.com'),
+ '- "example.com" must be a valid video URL',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::videoUrl('Vimeo')->assert('example.com'),
+ '- "example.com" must be a valid Vimeo video URL',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::videoUrl())->assert('https://youtu.be/netHLn9TScY'),
+ '- "https://youtu.be/netHLn9TScY" must not be a valid video URL',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::videoUrl('Vimeo'))->assert('https://vimeo.com/71787467'),
+ '- "https://vimeo.com/71787467" must not be a valid Vimeo video URL',
+));
diff --git a/tests/feature/Rules/VowelTest.php b/tests/feature/Rules/VowelTest.php
new file mode 100644
index 00000000..46e0a8a9
--- /dev/null
+++ b/tests/feature/Rules/VowelTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::vowel()->assert('b'),
+ '"b" must consist of vowels only',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::vowel('c')->assert('d'),
+ '"d" must consist of vowels and "c"',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::vowel())->assert('a'),
+ '"a" must not consist of vowels only',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::vowel('f'))->assert('e'),
+ '"e" must not consist of vowels or "f"',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::vowel()->assert('g'),
+ '- "g" must consist of vowels only',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::vowel('h')->assert('j'),
+ '- "j" must consist of vowels and "h"',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::not(v::vowel())->assert('i'),
+ '- "i" must not consist of vowels only',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::vowel('k'))->assert('o'),
+ '- "o" must not consist of vowels or "k"',
+));
diff --git a/tests/feature/Rules/WhenTest.php b/tests/feature/Rules/WhenTest.php
new file mode 100644
index 00000000..5d1fad7c
--- /dev/null
+++ b/tests/feature/Rules/WhenTest.php
@@ -0,0 +1,56 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('When valid use "then"', expectAll(
+ fn() => v::when(v::intVal(), v::positive(), v::notEmpty())->assert(-1),
+ '-1 must be a positive number',
+ '- -1 must be a positive number',
+ ['positive' => '-1 must be a positive number']
+));
+
+test('When invalid use "else"', expectAll(
+ fn() => v::when(v::intVal(), v::positive(), v::notEmpty())->assert(''),
+ 'The value must not be empty',
+ '- The value must not be empty',
+ ['notEmpty' => 'The value must not be empty']
+));
+
+test('When valid use "then" using single template', expectAll(
+ fn() => v::when(v::intVal(), v::positive(), v::notEmpty())->assert(-1, 'That did not go as planned'),
+ 'That did not go as planned',
+ '- That did not go as planned',
+ ['positive' => 'That did not go as planned']
+));
+
+test('When invalid use "else" using single template', expectAll(
+ fn() => v::when(v::intVal(), v::positive(), v::notEmpty())->assert('', 'That could have been better'),
+ 'That could have been better',
+ '- That could have been better',
+ ['notEmpty' => 'That could have been better']
+));
+
+test('When valid use "then" using array template', expectAll(
+ fn() => v::when(v::intVal(), v::positive(), v::notEmpty())->assert(-1, [
+ 'notEmpty' => '--Never shown--',
+ 'positive' => 'Not positive',
+ ]),
+ 'Not positive',
+ '- Not positive',
+ ['positive' => 'Not positive']
+));
+
+test('When invalid use "else" using array template', expectAll(
+ fn() => v::when(v::intVal(), v::positive(), v::notEmpty())->assert('', [
+ 'notEmpty' => 'Not empty',
+ 'positive' => '--Never shown--',
+ ]),
+ 'Not empty',
+ '- Not empty',
+ ['notEmpty' => 'Not empty']
+));
diff --git a/tests/feature/Rules/WritableTest.php b/tests/feature/Rules/WritableTest.php
new file mode 100644
index 00000000..e798f662
--- /dev/null
+++ b/tests/feature/Rules/WritableTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::writable()->assert('/path/of/a/valid/writable/file.txt'),
+ '"/path/of/a/valid/writable/file.txt" must be writable',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::not(v::writable())->assert('tests/fixtures/valid-image.png'),
+ '"tests/fixtures/valid-image.png" must not be writable',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::writable()->assert([]),
+ '- `[]` must be writable',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::not(v::writable())->assert('tests/fixtures/invalid-image.png'),
+ '- "tests/fixtures/invalid-image.png" must not be writable',
+));
diff --git a/tests/feature/Rules/XdigitTest.php b/tests/feature/Rules/XdigitTest.php
new file mode 100644
index 00000000..ae7f94e2
--- /dev/null
+++ b/tests/feature/Rules/XdigitTest.php
@@ -0,0 +1,48 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::xdigit()->assert('aaa%a'),
+ '"aaa%a" must only contain hexadecimal digits',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::xdigit(' ')->assert('bbb%b'),
+ '"bbb%b" must contain hexadecimal digits and " "',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::not(v::xdigit())->assert('ccccc'),
+ '"ccccc" must not only contain hexadecimal digits',
+));
+
+test('Scenario #4', expectMessage(
+ fn() => v::not(v::xdigit('% '))->assert('ddd%d'),
+ '"ddd%d" must not contain hexadecimal digits or "% "',
+));
+
+test('Scenario #5', expectFullMessage(
+ fn() => v::xdigit()->assert('eee^e'),
+ '- "eee^e" must only contain hexadecimal digits',
+));
+
+test('Scenario #6', expectFullMessage(
+ fn() => v::not(v::xdigit())->assert('fffff'),
+ '- "fffff" must not only contain hexadecimal digits',
+));
+
+test('Scenario #7', expectFullMessage(
+ fn() => v::xdigit('* &%')->assert('000^0'),
+ '- "000^0" must contain hexadecimal digits and "* &%"',
+));
+
+test('Scenario #8', expectFullMessage(
+ fn() => v::not(v::xdigit('^'))->assert('111^1'),
+ '- "111^1" must not contain hexadecimal digits or "^"',
+));
diff --git a/tests/feature/Rules/YesTest.php b/tests/feature/Rules/YesTest.php
new file mode 100644
index 00000000..64955704
--- /dev/null
+++ b/tests/feature/Rules/YesTest.php
@@ -0,0 +1,28 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessage(
+ fn() => v::not(v::yes())->assert('Yes'),
+ '"Yes" must not be similar to "Yes"',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::yes()->assert('si'),
+ '"si" must be similar to "Yes"',
+));
+
+test('Scenario #3', expectFullMessage(
+ fn() => v::not(v::yes())->assert('Yes'),
+ '- "Yes" must not be similar to "Yes"',
+));
+
+test('Scenario #4', expectFullMessage(
+ fn() => v::yes()->assert('si'),
+ '- "si" must be similar to "Yes"',
+));
diff --git a/tests/feature/SetTemplateWithMultipleValidatorsShouldUseTemplateAsFullMessageTest.php b/tests/feature/SetTemplateWithMultipleValidatorsShouldUseTemplateAsFullMessageTest.php
new file mode 100644
index 00000000..859becb6
--- /dev/null
+++ b/tests/feature/SetTemplateWithMultipleValidatorsShouldUseTemplateAsFullMessageTest.php
@@ -0,0 +1,17 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+test('Scenario #1', expectFullMessage(
+ function (): void {
+ Validator::callback('is_string')->between(1, 2)->setTemplate('{{name}} is not tasty')->assert('something');
+ },
+ '- "something" is not tasty',
+));
diff --git a/tests/feature/SetTemplateWithMultipleValidatorsShouldUseTemplateAsMainMessageTest.php b/tests/feature/SetTemplateWithMultipleValidatorsShouldUseTemplateAsMainMessageTest.php
new file mode 100644
index 00000000..29d3ef96
--- /dev/null
+++ b/tests/feature/SetTemplateWithMultipleValidatorsShouldUseTemplateAsMainMessageTest.php
@@ -0,0 +1,17 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+test('Scenario #1', expectMessage(
+ function (): void {
+ Validator::callback('is_int')->between(1, 2)->setTemplate('{{name}} is not tasty')->assert('something');
+ },
+ '"something" is not tasty',
+));
diff --git a/tests/feature/SetTemplateWithSingleValidatorShouldUseTemplateAsMainMessageTest.php b/tests/feature/SetTemplateWithSingleValidatorShouldUseTemplateAsMainMessageTest.php
new file mode 100644
index 00000000..1c6c19b7
--- /dev/null
+++ b/tests/feature/SetTemplateWithSingleValidatorShouldUseTemplateAsMainMessageTest.php
@@ -0,0 +1,15 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Validator;
+
+test('Scenario', expectMessage(
+ fn() => Validator::callback('is_int')->setTemplate('{{name}} is not tasty')->assert('something'),
+ '"something" is not tasty',
+));
diff --git a/tests/feature/ShouldNotOverwriteDefinedNamesTest.php b/tests/feature/ShouldNotOverwriteDefinedNamesTest.php
new file mode 100644
index 00000000..fa34d358
--- /dev/null
+++ b/tests/feature/ShouldNotOverwriteDefinedNamesTest.php
@@ -0,0 +1,25 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+$input = ['email' => 'not an email'];
+
+test('Scenario #1', expectMessage(
+ fn() => v::key('email', v::email()->setName('Email'))->setName('Foo')->assert($input),
+ 'Email must be a valid email address',
+));
+
+test('Scenario #2', expectMessage(
+ fn() => v::key('email', v::email())->setName('Email')->assert($input),
+ 'email must be a valid email address',
+));
+
+test('Scenario #3', expectMessage(
+ fn() => v::key('email', v::email())->assert($input),
+ 'email must be a valid email address',
+));
diff --git a/tests/feature/Transformers/AliasesTest.php b/tests/feature/Transformers/AliasesTest.php
new file mode 100644
index 00000000..eeef9da9
--- /dev/null
+++ b/tests/feature/Transformers/AliasesTest.php
@@ -0,0 +1,17 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+test('Optional', expectAll(
+ fn() => v::optional(v::scalarVal())->assert([]),
+ '`[]` must be a scalar value or must be undefined',
+ '- `[]` must be a scalar value or must be undefined',
+ ['undefOrScalarVal' => '`[]` must be a scalar value or must be undefined']
+));
diff --git a/tests/feature/Transformers/DeprecatedAgeTest.php b/tests/feature/Transformers/DeprecatedAgeTest.php
new file mode 100644
index 00000000..a38662bc
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedAgeTest.php
@@ -0,0 +1,58 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::minAge(18)->assert('17 years ago'),
+ 'The number of years between now and 17 years ago must be greater than or equal to 18',
+ 'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::not(v::minAge(18))->assert('-30 years'),
+ 'The number of years between now and -30 years must be less than 18',
+ 'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::minAge(18)->assert('yesterday'),
+ 'The number of years between now and yesterday must be greater than or equal to 18',
+ 'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::minAge(18, 'd/m/Y')->assert('12/10/2010'),
+ 'The number of years between now and 12/10/2010 must be greater than or equal to 18',
+ 'The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #5', expectMessageAndError(
+ fn() => v::maxAge(12)->assert('50 years ago'),
+ 'The number of years between now and 50 years ago must be less than or equal to 12',
+ 'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #6', expectMessageAndError(
+ fn() => v::not(v::maxAge(12))->assert('11 years ago'),
+ 'The number of years between now and 11 years ago must be greater than 12',
+ 'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #7', expectMessageAndError(
+ fn() => v::maxAge(12, 'Y-m-d')->assert('1988-09-09'),
+ 'The number of years between now and 1988-09-09 must be less than or equal to 12',
+ 'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
+
+test('Scenario #8', expectMessageAndError(
+ fn() => v::not(v::maxAge(12, 'Y-m-d'))->assert('2018-01-01'),
+ 'The number of years between now and 2018-01-01 must be greater than 12',
+ 'The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead.',
+));
diff --git a/tests/feature/Transformers/DeprecatedAttributeTest.php b/tests/feature/Transformers/DeprecatedAttributeTest.php
new file mode 100644
index 00000000..2989305a
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedAttributeTest.php
@@ -0,0 +1,60 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+$object = new stdClass();
+$object->foo = true;
+$object->bar = 42;
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::attribute('baz')->assert($object),
+ 'baz must be present',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyExists() instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::not(v::attribute('foo'))->assert($object),
+ 'foo must not be present',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyExists() instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::attribute('foo', v::falseVal())->assert($object),
+ 'foo must evaluate to `false`',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::not(v::attribute('foo', v::trueVal()))->assert($object),
+ 'foo must not evaluate to `true`',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.'
+));
+
+test('Scenario #5', expectMessageAndError(
+ fn() => v::attribute('foo', v::falseVal(), true)->assert($object),
+ 'foo must evaluate to `false`',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.'
+));
+
+test('Scenario #6', expectMessageAndError(
+ fn() => v::not(v::attribute('foo', v::trueVal(), true))->assert($object),
+ 'foo must not evaluate to `true`',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead.'
+));
+
+test('Scenario #7', expectMessageAndError(
+ fn() => v::attribute('foo', v::falseVal(), false)->assert($object),
+ 'foo must evaluate to `false`',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyOptional() instead.'
+));
+
+test('Scenario #8', expectMessageAndError(
+ fn() => v::not(v::attribute('foo', v::trueVal(), false))->assert($object),
+ 'foo must not evaluate to `true`',
+ 'The attribute() rule has been deprecated and will be removed in the next major version. Use propertyOptional() instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedKeyNestedTest.php b/tests/feature/Transformers/DeprecatedKeyNestedTest.php
new file mode 100644
index 00000000..baeaa9f9
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedKeyNestedTest.php
@@ -0,0 +1,38 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+$input = [
+ 'foo' => (object) [
+ 'bar' => 123,
+ ],
+];
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::keyNested('foo.bar.baz')->assert(['foo.bar.baz' => false]),
+ 'foo must be present',
+ 'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::keyNested('foo.bar', v::negative())->assert($input),
+ 'bar must be a negative number',
+ 'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::keyNested('foo.bar', v::stringType())->assert(new ArrayObject($input)),
+ 'bar must be a string',
+ 'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::keyNested('foo.bar', v::floatType(), false)->assert($input),
+ 'bar must be float',
+ 'The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedKeyTest.php b/tests/feature/Transformers/DeprecatedKeyTest.php
new file mode 100644
index 00000000..0948f83f
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedKeyTest.php
@@ -0,0 +1,46 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+$array = ['foo' => true, 'bar' => 42];
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::key('baz')->assert($array),
+ 'baz must be present',
+ 'Calling key() without a second parameter has been deprecated, and will be not be allowed in the next major version. Use keyExists() instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::not(v::key('foo'))->assert($array),
+ 'foo must not be present',
+ 'Calling key() without a second parameter has been deprecated, and will be not be allowed in the next major version. Use keyExists() instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::key('foo', v::falseVal(), true)->assert($array),
+ 'foo must evaluate to `false`',
+ 'Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use key() without the third parameter.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::not(v::key('foo', v::trueVal(), true))->assert($array),
+ 'foo must not evaluate to `true`',
+ 'Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use key() without the third parameter.'
+));
+
+test('Scenario #5', expectMessageAndError(
+ fn() => v::key('foo', v::falseVal(), false)->assert($array),
+ 'foo must evaluate to `false`',
+ 'Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use keyOptional() instead.'
+));
+
+test('Scenario #6', expectMessageAndError(
+ fn() => v::not(v::key('foo', v::trueVal(), false))->assert($array),
+ 'foo must not evaluate to `true`',
+ 'Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use keyOptional() instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedKeyValueTest.php b/tests/feature/Transformers/DeprecatedKeyValueTest.php
new file mode 100644
index 00000000..00e1a10e
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedKeyValueTest.php
@@ -0,0 +1,68 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::keyValue('foo', 'equals', 'bar')->assert(['bar' => 42]),
+ 'foo must be present',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 42]),
+ 'bar must be present',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::keyValue('foo', 'json', 'bar')->assert(['foo' => 42, 'bar' => 43]),
+ 'bar must be valid to validate foo',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 1, 'bar' => 2]),
+ 'foo must be equal to 2',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #5', expectMessageAndError(
+ fn() => v::not(v::keyValue('foo', 'equals', 'bar'))->assert(['foo' => 1, 'bar' => 1]),
+ 'foo must not be equal to 1',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #6', expectMessageAndError(
+ fn() => v::keyValue('foo', 'equals', 'bar')->assert(['bar' => 42]),
+ 'foo must be present',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #7', expectMessageAndError(
+ fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 42]),
+ 'bar must be present',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #8', expectMessageAndError(
+ fn() => v::keyValue('foo', 'json', 'bar')->assert(['foo' => 42, 'bar' => 43]),
+ 'bar must be valid to validate foo',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #9', expectMessageAndError(
+ fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 1, 'bar' => 2]),
+ 'foo must be equal to 2',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
+
+test('Scenario #10', expectMessageAndError(
+ fn() => v::not(v::keyValue('foo', 'equals', 'bar'))->assert(['foo' => 1, 'bar' => 1]),
+ 'foo must not be equal to 1',
+ 'The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedLengthTest.php b/tests/feature/Transformers/DeprecatedLengthTest.php
new file mode 100644
index 00000000..834775df
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedLengthTest.php
@@ -0,0 +1,154 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+require_once 'vendor/autoload.php';
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::length(0, 5, false)->assert('forest'),
+ 'The length of "forest" must be less than 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(5) instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::length(10, 20)->assert('river'),
+ 'The length of "river" must be between 10 and 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(10, 20) instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::length(15, null, false)->assert('mountain'),
+ 'The length of "mountain" must be greater than 15',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(15) instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::length(20)->assert('ocean'),
+ 'The length of "ocean" must be greater than or equal to 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(20) instead.'
+));
+
+test('Scenario #5', expectMessageAndError(
+ fn() => v::length(2, 5)->assert('desert'),
+ 'The length of "desert" must be between 2 and 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(2, 5) instead.'
+));
+
+test('Scenario #6', expectMessageAndError(
+ fn() => v::not(v::length(0, 15))->assert('rainforest'),
+ 'The length of "rainforest" must be greater than 15',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThanOrEqual(15) instead.'
+));
+
+test('Scenario #7', expectMessageAndError(
+ fn() => v::not(v::length(0, 20, false))->assert('glacier'),
+ 'The length of "glacier" must not be less than 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(20) instead.'
+));
+
+test('Scenario #8', expectMessageAndError(
+ fn() => v::not(v::length(3, null))->assert('meadow'),
+ 'The length of "meadow" must be less than 3',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(3) instead.'
+));
+
+test('Scenario #9', expectMessageAndError(
+ fn() => v::not(v::length(5, null, false))->assert('volcano'),
+ 'The length of "volcano" must not be greater than 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(5) instead.'
+));
+
+test('Scenario #10', expectMessageAndError(
+ fn() => v::not(v::length(5, 20))->assert('canyon'),
+ 'The length of "canyon" must not be between 5 and 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(5, 20) instead.'
+));
+
+test('Scenario #11', expectMessageAndError(
+ fn() => v::length(0, 5, false)->assert('prairie'),
+ 'The length of "prairie" must be less than 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(5) instead.'
+));
+
+test('Scenario #12', expectMessageAndError(
+ fn() => v::length(0, 5)->assert('wetland'),
+ 'The length of "wetland" must be less than or equal to 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThanOrEqual(5) instead.'
+));
+
+test('Scenario #13', expectMessageAndError(
+ fn() => v::length(15, null, false)->assert('tundra'),
+ 'The length of "tundra" must be greater than 15',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(15) instead.'
+));
+
+test('Scenario #14', expectMessageAndError(
+ fn() => v::length(20)->assert('savanna'),
+ 'The length of "savanna" must be greater than or equal to 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(20) instead.'
+));
+
+test('Scenario #15', expectMessageAndError(
+ fn() => v::length(7, 10)->assert('marsh'),
+ 'The length of "marsh" must be between 7 and 10',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(7, 10) instead.'
+));
+
+test('Scenario #16', expectMessageAndError(
+ fn() => v::length(4, 10, false)->assert('reef'),
+ 'The length of "reef" must be greater than 4 and less than 10',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetweenExclusive(4, 10) instead.'
+));
+
+test('Scenario #17', expectMessageAndError(
+ fn() => v::not(v::length(0, 15))->assert('valley'),
+ 'The length of "valley" must be greater than 15',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThanOrEqual(15) instead.'
+));
+
+test('Scenario #18', expectMessageAndError(
+ fn() => v::not(v::length(0, 20, false))->assert('island'),
+ 'The length of "island" must not be less than 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(20) instead.'
+));
+
+test('Scenario #19', expectMessageAndError(
+ fn() => v::not(v::length(5, null))->assert('plateau'),
+ 'The length of "plateau" must be less than 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(5) instead.'
+));
+
+test('Scenario #20', expectMessageAndError(
+ fn() => v::not(v::length(3, null, false))->assert('fjord'),
+ 'The length of "fjord" must not be greater than 3',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(3) instead.'
+));
+
+test('Scenario #21', expectMessageAndError(
+ fn() => v::not(v::length(5, 20))->assert('delta'),
+ 'The length of "delta" must not be between 5 and 20',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(5, 20) instead.'
+));
+
+test('Scenario #22', expectMessageAndError(
+ fn() => v::not(v::length(5, 11, false))->assert('waterfall'),
+ 'The length of "waterfall" must not be greater than 5 or less than 11',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetweenExclusive(5, 11) instead.'
+));
+
+test('Scenario #23', expectMessageAndError(
+ fn() => v::length(8, 8)->assert('estuary'),
+ 'The length of "estuary" must be equal to 8',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthEquals(8) instead.'
+));
+
+test('Scenario #24', expectMessageAndError(
+ fn() => v::not(v::length(5, 5))->assert('grove'),
+ 'The length of "grove" must not be equal to 5',
+ 'Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthEquals(5) instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedMaxTest.php b/tests/feature/Transformers/DeprecatedMaxTest.php
new file mode 100644
index 00000000..22a58881
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedMaxTest.php
@@ -0,0 +1,32 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::max(10)->assert(11),
+ '11 must be less than or equal to 10',
+ 'Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead.',
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::not(v::max(10))->assert(5),
+ '5 must be greater than 10',
+ 'Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead.',
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::max('today')->assert('tomorrow'),
+ '"tomorrow" must be less than or equal to "today"',
+ 'Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::not(v::max('b'))->assert('a'),
+ '"a" must be greater than "b"',
+ 'Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedMinTest.php b/tests/feature/Transformers/DeprecatedMinTest.php
new file mode 100644
index 00000000..650a1fd4
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedMinTest.php
@@ -0,0 +1,32 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::min(INF)->assert(10),
+ '10 must be greater than or equal to `INF`',
+ 'Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::not(v::min(5))->assert(INF),
+ '`INF` must be less than 5',
+ 'Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::min('today')->assert('yesterday'),
+ '"yesterday" must be greater than or equal to "today"',
+ 'Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::not(v::min('a'))->assert('z'),
+ '"z" must be less than "a"',
+ 'Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead.'
+));
diff --git a/tests/feature/Transformers/DeprecatedTypeTest.php b/tests/feature/Transformers/DeprecatedTypeTest.php
new file mode 100644
index 00000000..5540908b
--- /dev/null
+++ b/tests/feature/Transformers/DeprecatedTypeTest.php
@@ -0,0 +1,80 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+test('Scenario #1', expectMessageAndError(
+ fn() => v::type('array')->assert(1),
+ '1 must be an array',
+ 'The type() rule is deprecated and will be removed in the next major version. Use arrayType() instead.'
+));
+
+test('Scenario #2', expectMessageAndError(
+ fn() => v::type('bool')->assert(1),
+ '1 must be a boolean',
+ 'The type() rule is deprecated and will be removed in the next major version. Use boolType() instead.'
+));
+
+test('Scenario #3', expectMessageAndError(
+ fn() => v::type('boolean')->assert(1),
+ '1 must be a boolean',
+ 'The type() rule is deprecated and will be removed in the next major version. Use boolType() instead.'
+));
+
+test('Scenario #4', expectMessageAndError(
+ fn() => v::type('callable')->assert(1),
+ '1 must be a callable',
+ 'The type() rule is deprecated and will be removed in the next major version. Use callableType() instead.'
+));
+
+test('Scenario #5', expectMessageAndError(
+ fn() => v::type('double')->assert(1),
+ '1 must be float',
+ 'The type() rule is deprecated and will be removed in the next major version. Use floatType() instead.'
+));
+
+test('Scenario #6', expectMessageAndError(
+ fn() => v::type('float')->assert(1),
+ '1 must be float',
+ 'The type() rule is deprecated and will be removed in the next major version. Use floatType() instead.'
+));
+
+test('Scenario #7', expectMessageAndError(
+ fn() => v::type('int')->assert('1'),
+ '"1" must be an integer',
+ 'The type() rule is deprecated and will be removed in the next major version. Use intType() instead.'
+));
+
+test('Scenario #8', expectMessageAndError(
+ fn() => v::type('integer')->assert('1'),
+ '"1" must be an integer',
+ 'The type() rule is deprecated and will be removed in the next major version. Use intType() instead.'
+));
+
+test('Scenario #9', expectMessageAndError(
+ fn() => v::type('null')->assert(1),
+ '1 must be null',
+ 'The type() rule is deprecated and will be removed in the next major version. Use nullType() instead.'
+));
+
+test('Scenario #10', expectMessageAndError(
+ fn() => v::type('object')->assert(1),
+ '1 must be an object',
+ 'The type() rule is deprecated and will be removed in the next major version. Use objectType() instead.'
+));
+
+test('Scenario #11', expectMessageAndError(
+ fn() => v::type('resource')->assert(1),
+ '1 must be a resource',
+ 'The type() rule is deprecated and will be removed in the next major version. Use resourceType() instead.'
+));
+
+test('Scenario #12', expectMessageAndError(
+ fn() => v::type('string')->assert(1),
+ '1 must be a string',
+ 'The type() rule is deprecated and will be removed in the next major version. Use stringType() instead.'
+));
diff --git a/tests/feature/Transformers/PrefixTest.php b/tests/feature/Transformers/PrefixTest.php
new file mode 100644
index 00000000..14c583d3
--- /dev/null
+++ b/tests/feature/Transformers/PrefixTest.php
@@ -0,0 +1,66 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+test('Key', expectAll(
+ fn() => v::keyEquals('foo', 12)->assert(['foo' => 10]),
+ 'foo must be equal to 12',
+ '- foo must be equal to 12',
+ ['foo' => 'foo must be equal to 12']
+));
+
+test('Length', expectAll(
+ fn() => v::lengthGreaterThan(3)->assert('foo'),
+ 'The length of "foo" must be greater than 3',
+ '- The length of "foo" must be greater than 3',
+ ['lengthGreaterThan' => 'The length of "foo" must be greater than 3']
+));
+
+test('Max', expectAll(
+ fn() => v::maxOdd()->assert([1, 2, 3, 4]),
+ 'As the maximum of `[1, 2, 3, 4]`, 4 must be an odd number',
+ '- As the maximum of `[1, 2, 3, 4]`, 4 must be an odd number',
+ ['maxOdd' => 'As the maximum of `[1, 2, 3, 4]`, 4 must be an odd number']
+));
+
+test('Min', expectAll(
+ fn() => v::minEven()->assert([1, 2, 3]),
+ 'As the minimum from `[1, 2, 3]`, 1 must be an even number',
+ '- As the minimum from `[1, 2, 3]`, 1 must be an even number',
+ ['minEven' => 'As the minimum from `[1, 2, 3]`, 1 must be an even number']
+));
+
+test('Not', expectAll(
+ fn() => v::notBetween(1, 3)->assert(2),
+ '2 must not be between 1 and 3',
+ '- 2 must not be between 1 and 3',
+ ['notBetween' => '2 must not be between 1 and 3']
+));
+
+test('NullOr', expectAll(
+ fn() => v::nullOrBoolType()->assert('string'),
+ '"string" must be a boolean or must be null',
+ '- "string" must be a boolean or must be null',
+ ['nullOrBoolType' => '"string" must be a boolean or must be null']
+));
+
+test('Property', expectAll(
+ fn() => v::propertyBetween('foo', 1, 3)->assert((object) ['foo' => 5]),
+ 'foo must be between 1 and 3',
+ '- foo must be between 1 and 3',
+ ['foo' => 'foo must be between 1 and 3']
+));
+
+test('UndefOr', expectAll(
+ fn() => v::undefOrUrl()->assert('string'),
+ '"string" must be a URL or must be undefined',
+ '- "string" must be a URL or must be undefined',
+ ['undefOrUrl' => '"string" must be a URL or must be undefined']
+));
diff --git a/tests/feature/TranslatorTest.php b/tests/feature/TranslatorTest.php
new file mode 100644
index 00000000..1af02d39
--- /dev/null
+++ b/tests/feature/TranslatorTest.php
@@ -0,0 +1,47 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+use Respect\Validation\Message\Translator\ArrayTranslator;
+use Respect\Validation\Validator;
+use Respect\Validation\ValidatorDefaults;
+
+test('Scenario #1', expectFullMessage(
+ function (): void {
+ ValidatorDefaults::setTranslator(new ArrayTranslator([
+ 'All of the required rules must pass for {{name}}' => 'Todas as regras requeridas devem passar para {{name}}',
+ 'The length of' => 'O comprimento de',
+ '{{name}} must be of type string' => '{{name}} deve ser do tipo string',
+ '{{name}} must be between {{minValue}} and {{maxValue}}' => '{{name}} deve possuir de {{minValue}} a {{maxValue}} caracteres',
+ '{{name}} must be a valid telephone number for country {{countryName|trans}}'
+ => '{{name}} deve ser um número de telefone válido para o país {{countryName|trans}}',
+ 'United States' => 'Estados Unidos',
+ ]));
+
+ Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert(0);
+ },
+ <<<'FULL_MESSAGE'
+ - Todas as regras requeridas devem passar para 0
+ - 0 must be a string
+ - O comprimento de 0 deve possuir de 2 a 15 caracteres
+ - 0 deve ser um número de telefone válido para o país Estados Unidos
+ FULL_MESSAGE,
+));
+
+test('Scenario #2', expectMessage(
+ function (): void {
+ ValidatorDefaults::setTranslator(new ArrayTranslator([
+ 'years' => 'anos',
+ 'The number of {{type|trans}} between now and' => 'O número de {{type|trans}} entre agora e',
+ '{{name}} must be equal to {{compareTo}}' => '{{name}} deve ser igual a {{compareTo}}',
+ ]));
+
+ v::dateTimeDiff('years', v::equals(2))->assert('1972-02-09');
+ },
+ 'O número de anos entre agora e 1972-02-09 deve ser igual a 2',
+));
diff --git a/tests/integration/assert-with-properties.phpt b/tests/integration/assert-with-properties.phpt
deleted file mode 100644
index f2c2c7c3..00000000
--- a/tests/integration/assert-with-properties.phpt
+++ /dev/null
@@ -1,48 +0,0 @@
---FILE--
- [
- 'host' => 42,
- 'user' => 'user',
- 'password' => 'password',
- 'schema' => 'schema',
- ],
- 'postgresql' => [
- 'host' => 'host',
- 'user' => 42,
- 'password' => 'password',
- 'schema' => 'schema',
- ],
- ];
- $object = json_decode((string) json_encode($array));
- v::create()
- ->property(
- 'mysql',
- v::create()
- ->property('host', v::stringType())
- ->property('user', v::stringType())
- ->property('password', v::stringType())
- ->property('schema', v::stringType())
- )
- ->property(
- 'postgresql',
- v::create()
- ->property('host', v::stringType())
- ->property('user', v::stringType())
- ->property('password', v::stringType())
- ->property('schema', v::stringType())
- )
- ->setName('the given data')
- ->assert($object);
-});
-?>
---EXPECT--
-- All of the required rules must pass for the given data
- - These rules must pass for mysql
- - host must be a string
- - These rules must pass for postgresql
- - user must be a string
\ No newline at end of file
diff --git a/tests/integration/assert-with-templates.phpt b/tests/integration/assert-with-templates.phpt
deleted file mode 100644
index 82ef6eae..00000000
--- a/tests/integration/assert-with-templates.phpt
+++ /dev/null
@@ -1,84 +0,0 @@
---FILE--
- v::alwaysInvalid()->setTemplate('My string template in the chain')->assert(1)
-);
-
-exceptionAll(
- 'Template as an array in the chain',
- static fn() => v::alwaysInvalid()->setTemplates(['alwaysInvalid' => 'My array template in the chain'])->assert(1)
-);
-
-exceptionAll(
- 'Runtime template as string',
- static fn() => v::alwaysInvalid()->assert(1, 'My runtime template as string')
-);
-
-exceptionAll(
- 'Runtime template as an array',
- static fn() => v::alwaysInvalid()->assert(1, ['alwaysInvalid' => 'My runtime template an array'])
-);
-
-heading('Runtime template as an exception');
-try {
- v::alwaysInvalid()->assert(1, new Exception('My runtime template as an exception'));
-} catch (Throwable $exception) {
- echo $exception->getMessage();
- echo PHP_EOL . PHP_EOL;
-}
-
-heading('Runtime template as a callable');
-try {
- v::alwaysInvalid()
- ->assert(1, static fn(ValidationException $exception) => new Exception('My runtime template as an exception: ' . $exception->getMessage()));
-} catch (Throwable $exception) {
- echo $exception->getMessage();
- echo PHP_EOL . PHP_EOL;
-}
-?>
---EXPECT--
-Template as a string in the chain
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-My string template in the chain
-- My string template in the chain
-[
- 'alwaysInvalid' => 'My string template in the chain',
-]
-
-Template as an array in the chain
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-My array template in the chain
-- My array template in the chain
-[
- 'alwaysInvalid' => 'My array template in the chain',
-]
-
-Runtime template as string
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-My runtime template as string
-- My runtime template as string
-[
- 'alwaysInvalid' => 'My runtime template as string',
-]
-
-Runtime template as an array
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-My runtime template an array
-- My runtime template an array
-[
- 'alwaysInvalid' => 'My runtime template an array',
-]
-
-Runtime template as an exception
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-My runtime template as an exception
-
-Runtime template as a callable
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-My runtime template as an exception: 1 must be valid
diff --git a/tests/integration/do_not_rely_on_nested_validation_exception_interface_for_check.phpt b/tests/integration/do_not_rely_on_nested_validation_exception_interface_for_check.phpt
deleted file mode 100644
index 10e3020a..00000000
--- a/tests/integration/do_not_rely_on_nested_validation_exception_interface_for_check.phpt
+++ /dev/null
@@ -1,13 +0,0 @@
---FILE--
- Validator::alnum('__')->lengthBetween(1, 15)->noWhitespace()->assert('really messed up screen#name')
-);
-?>
---EXPECT--
-"really messed up screen#name" must contain only letters (a-z), digits (0-9), and "__"
\ No newline at end of file
diff --git a/tests/integration/get_full_message_should_include_all_validation_messages_in_a_chain.phpt b/tests/integration/get_full_message_should_include_all_validation_messages_in_a_chain.phpt
deleted file mode 100644
index 2109b8e5..00000000
--- a/tests/integration/get_full_message_should_include_all_validation_messages_in_a_chain.phpt
+++ /dev/null
@@ -1,13 +0,0 @@
---FILE--
- Validator::stringType()->lengthBetween(2, 15)->assert(0));
-?>
---EXPECT--
-- All of the required rules must pass for 0
- - 0 must be a string
- - The length of 0 must be between 2 and 15
\ No newline at end of file
diff --git a/tests/integration/get_messages.phpt b/tests/integration/get_messages.phpt
deleted file mode 100644
index 0617868c..00000000
--- a/tests/integration/get_messages.phpt
+++ /dev/null
@@ -1,53 +0,0 @@
---FILE--
-key(
- 'mysql',
- v::create()
- ->key('host', v::stringType())
- ->key('user', v::stringType())
- ->key('password', v::stringType())
- ->key('schema', v::stringType())
- )
- ->key(
- 'postgresql',
- v::create()
- ->key('host', v::stringType())
- ->key('user', v::stringType())
- ->key('password', v::stringType())
- ->key('schema', v::stringType())
- )
- ->assert([
- 'mysql' => [
- 'host' => 42,
- 'schema' => 42,
- ],
- 'postgresql' => [
- 'user' => 42,
- 'password' => 42,
- ],
- ]);
-});
-?>
---EXPECT--
-[
- '__root__' => 'All of the required rules must pass for `["mysql": ["host": 42, "schema": 42], "postgresql": ["user": 42, "password": 42]]`',
- 'mysql' => [
- '__root__' => 'All of the required rules must pass for mysql',
- 'host' => 'host must be a string',
- 'user' => 'user must be present',
- 'password' => 'password must be present',
- 'schema' => 'schema must be a string',
- ],
- 'postgresql' => [
- '__root__' => 'All of the required rules must pass for postgresql',
- 'host' => 'host must be present',
- 'user' => 'user must be a string',
- 'password' => 'password must be a string',
- 'schema' => 'schema must be present',
- ],
-]
\ No newline at end of file
diff --git a/tests/integration/get_messages_should_include_all_validation_messages_in_a_chain.phpt b/tests/integration/get_messages_should_include_all_validation_messages_in_a_chain.phpt
deleted file mode 100644
index e97144d3..00000000
--- a/tests/integration/get_messages_should_include_all_validation_messages_in_a_chain.phpt
+++ /dev/null
@@ -1,32 +0,0 @@
---FILE--
- 'u',
- 'birthdate' => 'Not a date',
- 'password' => '',
- ];
-
- Validator::create()
- ->key('username', Validator::lengthBetween(2, 32))
- ->key('birthdate', Validator::dateTime())
- ->key('password', Validator::notEmpty())
- ->key('email', Validator::email())
- ->assert($input);
-});
-?>
---EXPECT--
-[
- '__root__' => 'All of the required rules must pass for `["username": "u", "birthdate": "Not a date", "password": ""]`',
- 'username' => 'The length of username must be between 2 and 32',
- 'birthdate' => 'birthdate must be a valid date/time',
- 'password' => 'password must not be empty',
- 'email' => 'email must be present',
-]
diff --git a/tests/integration/issues/1033.phpt b/tests/integration/issues/1033.phpt
deleted file mode 100644
index b9bfa44d..00000000
--- a/tests/integration/issues/1033.phpt
+++ /dev/null
@@ -1,24 +0,0 @@
---FILE--
- v::each(v::equals(1))->assert(['A', 'B', 'B'])
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1033
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"A" must be equal to 1
-- Each item in `["A", "B", "B"]` must be valid
- - "A" must be equal to 1
- - "B" must be equal to 1
- - "B" must be equal to 1
-[
- '__root__' => 'Each item in `["A", "B", "B"]` must be valid',
- 'equals.1' => '"A" must be equal to 1',
- 'equals.2' => '"B" must be equal to 1',
- 'equals.3' => '"B" must be equal to 1',
-]
diff --git a/tests/integration/issues/1244.phpt b/tests/integration/issues/1244.phpt
deleted file mode 100644
index 40674dc5..00000000
--- a/tests/integration/issues/1244.phpt
+++ /dev/null
@@ -1,18 +0,0 @@
---FILE--
- v::key('firstname', v::notBlank()->setName('First Name'))->assert([])
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1244
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-First Name must be present
-- First Name must be present
-[
- 'firstname' => 'First Name must be present',
-]
diff --git a/tests/integration/issues/1289.phpt b/tests/integration/issues/1289.phpt
deleted file mode 100644
index d5341202..00000000
--- a/tests/integration/issues/1289.phpt
+++ /dev/null
@@ -1,65 +0,0 @@
---FILE--
- 2,
- 'description' => [],
- 'children' => ['nope'],
- ],
-];
-exceptionAll('https://github.com/Respect/Validation/issues/1289', static fn() => $validator->assert($input));
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1289
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-default must be a string
-- These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`
- - Only one of these rules must pass for default
- - default must be a string
- - default must be a boolean
- - description must be a string value
-[
- 'allOf' => [
- '__root__' => 'These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`',
- 'default' => [
- '__root__' => 'Only one of these rules must pass for default',
- 'stringType' => 'default must be a string',
- 'boolType' => 'default must be a boolean',
- ],
- 'description' => 'description must be a string value',
- ],
-]
diff --git a/tests/integration/issues/1333.phpt b/tests/integration/issues/1333.phpt
deleted file mode 100644
index ffd0c377..00000000
--- a/tests/integration/issues/1333.phpt
+++ /dev/null
@@ -1,22 +0,0 @@
---FILE--
- v::noWhitespace()->email()->setName('User Email')->assert('not email')
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1333
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-User Email must not contain whitespaces
-- All of the required rules must pass for User Email
- - User Email must not contain whitespaces
- - User Email must be a valid email address
-[
- '__root__' => 'All of the required rules must pass for User Email',
- 'noWhitespace' => 'User Email must not contain whitespaces',
- 'email' => 'User Email must be a valid email address',
-]
diff --git a/tests/integration/issues/1334.phpt b/tests/integration/issues/1334.phpt
deleted file mode 100644
index 33e78508..00000000
--- a/tests/integration/issues/1334.phpt
+++ /dev/null
@@ -1,46 +0,0 @@
---FILE--
-iterableType()->each(
- v::key('street', v::stringType()->notEmpty())
- ->key('region', v::stringType()->notEmpty())
- ->key('country', v::stringType()->notEmpty())
- ->keyOptional('other', v::nullOr(v::notEmpty()->stringType()))
- )->assert(
- [
- ['region' => 'Oregon', 'country' => 'USA', 'other' => 123],
- ['street' => '', 'region' => 'Oregon', 'country' => 'USA'],
- ['street' => 123, 'region' => 'Oregon', 'country' => 'USA'],
- ]
- );
-});
-
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1334
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-street must be present
-- Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid
- - These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`
- - street must be present
- - These rules must pass for other
- - other must be a string or must be null
- - These rules must pass for `["street": "", "region": "Oregon", "country": "USA"]`
- - street must not be empty
- - These rules must pass for `["street": 123, "region": "Oregon", "country": "USA"]`
- - street must be a string
-[
- 'each' => [
- '__root__' => 'Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid',
- 'allOf.1' => [
- '__root__' => 'These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`',
- 'street' => 'street must be present',
- 'other' => 'other must be a string or must be null',
- ],
- 'allOf.2' => 'street must not be empty',
- 'allOf.3' => 'street must be a string',
- ],
-]
diff --git a/tests/integration/issues/1348.phpt b/tests/integration/issues/1348.phpt
deleted file mode 100644
index 166a0739..00000000
--- a/tests/integration/issues/1348.phpt
+++ /dev/null
@@ -1,84 +0,0 @@
---FILE--
- 'Honda', 'model' => 'Accord'],
- ['manufacturer' => 'Toyota', 'model' => 'Rav4'],
- ['manufacturer' => 'Ford', 'model' => 'not real'],
- ['manufacturer' => 'Honda', 'model' => 'not valid'],
-];
-
-exceptionAll(
- 'https://github.com/Respect/Validation/issues/1289',
- static fn () => Validator::arrayType()->each(
- Validator::oneOf(
- Validator::key('manufacturer', Validator::equals('Honda'))
- ->key('model', Validator::in(['Accord', 'Fit'])),
- Validator::key('manufacturer', Validator::equals('Toyota'))
- ->key('model', Validator::in(['Rav4', 'Camry'])),
- Validator::key('manufacturer', Validator::equals('Ford'))
- ->key('model', Validator::in(['F150', 'Bronco']))
- )
- )->assert($cars)
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1289
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-manufacturer must be equal to "Honda"
-- Each item in `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]` must be valid
- - Only one of these rules must pass for `["manufacturer": "Ford", "model": "not real"]`
- - All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`
- - manufacturer must be equal to "Honda"
- - model must be in `["Accord", "Fit"]`
- - All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`
- - manufacturer must be equal to "Toyota"
- - model must be in `["Rav4", "Camry"]`
- - These rules must pass for `["manufacturer": "Ford", "model": "not real"]`
- - model must be in `["F150", "Bronco"]`
- - Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
- - These rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
- - model must be in `["Accord", "Fit"]`
- - All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
- - manufacturer must be equal to "Toyota"
- - model must be in `["Rav4", "Camry"]`
- - All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`
- - manufacturer must be equal to "Ford"
- - model must be in `["F150", "Bronco"]`
-[
- 'each' => [
- '__root__' => 'Each item in `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]` must be valid',
- 'oneOf.3' => [
- '__root__' => 'Only one of these rules must pass for `["manufacturer": "Ford", "model": "not real"]`',
- 'allOf.1' => [
- '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`',
- 'manufacturer' => 'manufacturer must be equal to "Honda"',
- 'model' => 'model must be in `["Accord", "Fit"]`',
- ],
- 'allOf.2' => [
- '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`',
- 'manufacturer' => 'manufacturer must be equal to "Toyota"',
- 'model' => 'model must be in `["Rav4", "Camry"]`',
- ],
- 'allOf.3' => 'model must be in `["F150", "Bronco"]`',
- ],
- 'oneOf.4' => [
- '__root__' => 'Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`',
- 'allOf.1' => 'model must be in `["Accord", "Fit"]`',
- 'allOf.2' => [
- '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`',
- 'manufacturer' => 'manufacturer must be equal to "Toyota"',
- 'model' => 'model must be in `["Rav4", "Camry"]`',
- ],
- 'allOf.3' => [
- '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`',
- 'manufacturer' => 'manufacturer must be equal to "Ford"',
- 'model' => 'model must be in `["F150", "Bronco"]`',
- ],
- ],
- ],
-]
diff --git a/tests/integration/issues/1376.phpt b/tests/integration/issues/1376.phpt
deleted file mode 100644
index cf710610..00000000
--- a/tests/integration/issues/1376.phpt
+++ /dev/null
@@ -1,36 +0,0 @@
---FILE--
-stringType())
- ->property('description', v::stringType())
- ->property('author', v::intType()->lengthBetween(1, 2))
- ->property('user', v::intVal()->lengthBetween(1, 2))
- ->assert((object) ['author' => 'foo']);
-});
-
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1376
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-title must be present
-- All of the required rules must pass for `stdClass { +$author="foo" }`
- - title must be present
- - description must be present
- - All of the required rules must pass for author
- - author must be an integer
- - The length of author must be between 1 and 2
- - user must be present
-[
- '__root__' => 'All of the required rules must pass for `stdClass { +$author="foo" }`',
- 'title' => 'title must be present',
- 'description' => 'description must be present',
- 'author' => [
- '__root__' => 'All of the required rules must pass for author',
- 'intType' => 'author must be an integer',
- 'lengthBetween' => 'The length of author must be between 1 and 2',
- ],
- 'user' => 'user must be present',
-]
diff --git a/tests/integration/issues/1469.phpt b/tests/integration/issues/1469.phpt
deleted file mode 100644
index fb0798bf..00000000
--- a/tests/integration/issues/1469.phpt
+++ /dev/null
@@ -1,50 +0,0 @@
---FILE--
- [
- [
- 'product_title' => 'test',
- 'quantity' => 'test',
- ],
- [
- 'product_title2' => 'test',
- ],
- ],
- ];
-
- v::arrayVal()->keySet(
- v::key('order_items', v::arrayVal()->each(v::keySet(
- v::key('product_title', v::stringVal()->notEmpty()),
- v::key('quantity', v::intVal()->notEmpty()),
- ))->notEmpty()),
- )->assert($data);
-});
-
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1469
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-quantity must be an integer value
-- Each item in order_items must be valid
- - order_items validation failed
- - quantity must be an integer value
- - order_items contains both missing and extra keys
- - product_title must be present
- - quantity must be present
- - product_title2 must not be present
-[
- 'keySet' => [
- '__root__' => 'Each item in order_items must be valid',
- 'keySet.1' => 'quantity must be an integer value',
- 'keySet.2' => [
- '__root__' => 'order_items contains both missing and extra keys',
- 'product_title' => 'product_title must be present',
- 'quantity' => 'quantity must be present',
- 'product_title2' => 'product_title2 must not be present',
- ],
- ],
-]
diff --git a/tests/integration/issues/1477.phpt b/tests/integration/issues/1477.phpt
deleted file mode 100644
index b4865256..00000000
--- a/tests/integration/issues/1477.phpt
+++ /dev/null
@@ -1,28 +0,0 @@
---FILE--
-setTemplate('{{name}} is not good!')
- )->assert(['Address' => 'cvejvn']);
-});
-
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/1477
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Address is not good!
-- Address is not good!
-[
- 'Address' => 'Address is not good!',
-]
diff --git a/tests/integration/issues/179.phpt b/tests/integration/issues/179.phpt
deleted file mode 100644
index 0a025959..00000000
--- a/tests/integration/issues/179.phpt
+++ /dev/null
@@ -1,32 +0,0 @@
---FILE--
- 1,
- 'password' => 'my_password',
- 'schema' => 'my_schema',
-];
-
-$validator = v::arrayType();
-$validator->setName('Settings');
-$validator->key('host', v::stringType());
-$validator->key('user', v::stringType());
-$validator->key('password', v::stringType());
-$validator->key('schema', v::stringType());
-
-exceptionAll('https://github.com/Respect/Validation/issues/179', static fn() => $validator->assert($config));
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/179
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-host must be a string
-- These rules must pass for Settings
- - host must be a string
- - user must be present
-[
- '__root__' => 'These rules must pass for Settings',
- 'host' => 'host must be a string',
- 'user' => 'user must be present',
-]
diff --git a/tests/integration/issues/425.phpt b/tests/integration/issues/425.phpt
deleted file mode 100644
index 0d3d2d4e..00000000
--- a/tests/integration/issues/425.phpt
+++ /dev/null
@@ -1,28 +0,0 @@
---FILE--
-key('age', v::intType()->notEmpty()->noneOf(v::stringType(), v::arrayType()))
- ->key('reference', v::stringType()->notEmpty()->lengthBetween(1, 50));
-
-exceptionAll('https://github.com/Respect/Validation/issues/425', static fn() => $validator->assert(['age' => 1]));
-exceptionAll('https://github.com/Respect/Validation/issues/425', static fn() => $validator->assert(['reference' => 'QSF1234']));
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/425
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-reference must be present
-- reference must be present
-[
- 'reference' => 'reference must be present',
-]
-
-https://github.com/Respect/Validation/issues/425
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-age must be present
-- age must be present
-[
- 'age' => 'age must be present',
-]
diff --git a/tests/integration/issues/446.phpt b/tests/integration/issues/446.phpt
deleted file mode 100644
index 1c2674ac..00000000
--- a/tests/integration/issues/446.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---FILE--
- 'w',
- 'email' => 'hello@hello.com',
-];
-
-exceptionAll('https://github.com/Respect/Validation/issues/446', static function () use ($arr): void {
- v::create()
- ->key('name', v::lengthBetween(2, 32))
- ->key('email', v::email())
- ->assert($arr);
-});
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/446
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The length of name must be between 2 and 32
-- The length of name must be between 2 and 32
-[
- 'name' => 'The length of name must be between 2 and 32',
-]
diff --git a/tests/integration/issues/619.phpt b/tests/integration/issues/619.phpt
deleted file mode 100644
index 228cde37..00000000
--- a/tests/integration/issues/619.phpt
+++ /dev/null
@@ -1,18 +0,0 @@
---FILE--
- v::instance(stdClass::class)->setTemplate('invalid object')->assert('test')
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/619
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-invalid object
-- invalid object
-[
- 'instance' => 'invalid object',
-]
diff --git a/tests/integration/issues/739.phpt b/tests/integration/issues/739.phpt
deleted file mode 100644
index 629dfe1a..00000000
--- a/tests/integration/issues/739.phpt
+++ /dev/null
@@ -1,18 +0,0 @@
---FILE--
- v::when(v::alwaysInvalid(), v::alwaysValid())->assert('foo')
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/739
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"foo" is invalid
-- "foo" is invalid
-[
- 'alwaysInvalid' => '"foo" is invalid',
-]
diff --git a/tests/integration/issues/799.phpt b/tests/integration/issues/799.phpt
deleted file mode 100644
index c8142e45..00000000
--- a/tests/integration/issues/799.phpt
+++ /dev/null
@@ -1,49 +0,0 @@
---FILE--
-call(
- [new CountableStub(1), 'count'],
- v::arrayVal()->key('scheme', v::startsWith('https'))
- )
- ->assert($input);
-});
-
-exceptionAll('https://github.com/Respect/Validation/issues/799', static function () use ($input): void {
- v::create()
- ->call(
- static function ($url) {
- return parse_url($url);
- },
- v::arrayVal()->key('scheme', v::startsWith('https'))
- )
- ->assert($input);
-});
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/799
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-1 must be an array value
-- All of the required rules must pass for 1
- - 1 must be an array value
- - scheme must be present
-[
- '__root__' => 'All of the required rules must pass for 1',
- 'arrayVal' => '1 must be an array value',
- 'scheme' => 'scheme must be present',
-]
-
-https://github.com/Respect/Validation/issues/799
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-scheme must start with "https"
-- scheme must start with "https"
-[
- 'scheme' => 'scheme must start with "https"',
-]
diff --git a/tests/integration/issues/805.phpt b/tests/integration/issues/805.phpt
deleted file mode 100644
index 26b74ef2..00000000
--- a/tests/integration/issues/805.phpt
+++ /dev/null
@@ -1,18 +0,0 @@
---FILE--
- v::key('email', v::email()->setTemplate('WRONG EMAIL!!!!!!'))->assert(['email' => 'qwe'])
-);
-?>
---EXPECT--
-https://github.com/Respect/Validation/issues/805
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-WRONG EMAIL!!!!!!
-- WRONG EMAIL!!!!!!
-[
- 'email' => 'WRONG EMAIL!!!!!!',
-]
diff --git a/tests/integration/keys_as_validator_names.phpt b/tests/integration/keys_as_validator_names.phpt
deleted file mode 100644
index eb54d505..00000000
--- a/tests/integration/keys_as_validator_names.phpt
+++ /dev/null
@@ -1,21 +0,0 @@
---FILE--
-key('username', Validator::length(Validator::between(2, 32)))
- ->key('birthdate', Validator::dateTime())
- ->setName('User Subscription Form')
- ->assert(['username' => '0', 'birthdate' => 'Whatever']);
-});
-?>
---EXPECT--
-- All of the required rules must pass for User Subscription Form
- - The length of username must be between 2 and 32
- - birthdate must be a valid date/time
diff --git a/tests/integration/lib/helpers.php b/tests/integration/lib/helpers.php
deleted file mode 100644
index 2a3ec01b..00000000
--- a/tests/integration/lib/helpers.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
- * SPDX-License-Identifier: MIT
- */
-
-declare(strict_types=1);
-
-use Respect\Validation\Exceptions\ValidationException;
-use Respect\Validation\Rule;
-use Symfony\Component\VarExporter\VarExporter;
-
-function heading(string $heading): void
-{
- echo $heading . PHP_EOL;
- echo str_repeat('⎺', strlen($heading)) . PHP_EOL;
-}
-
-function exceptionAll(string $heading, callable $callable): void
-{
- heading($heading);
- exceptionMessage($callable, 'No exception was thrown');
- exceptionFullMessage($callable, 'No exception was thrown');
- exceptionMessages($callable, 'No exception was thrown');
- echo PHP_EOL;
-}
-
-function exceptionMessage(callable $callable, string $fallbackMessage = 'No exception was thrown'): void
-{
- try {
- $callable();
- echo $fallbackMessage . PHP_EOL;
- } catch (ValidationException $exception) {
- echo $exception->getMessage() . PHP_EOL;
- }
-}
-
-function exceptionMessages(callable $callable, string $fallbackMessage = 'No exception was thrown'): void
-{
- try {
- $callable();
- echo $fallbackMessage . PHP_EOL;
- } catch (ValidationException $exception) {
- echo VarExporter::export($exception->getMessages()) . PHP_EOL;
- }
-}
-
-function exceptionFullMessage(callable $callable, string $fallbackMessage = 'No exception was thrown'): void
-{
- try {
- $callable();
- echo $fallbackMessage . PHP_EOL;
- } catch (ValidationException $exception) {
- echo $exception->getFullMessage() . PHP_EOL;
- }
-}
-
-/** @param array}> $scenarios */
-function run(array $scenarios): void
-{
- foreach ($scenarios as $heading => $data) {
- [$rule, $input, $template] = array_pad($data, 3, null);
- exceptionAll($heading, static fn() => $rule->assert($input, $template));
- }
-}
diff --git a/tests/integration/not_should_work_by_builder.phpt b/tests/integration/not_should_work_by_builder.phpt
deleted file mode 100644
index 4718e2d5..00000000
--- a/tests/integration/not_should_work_by_builder.phpt
+++ /dev/null
@@ -1,13 +0,0 @@
---TEST--
-not() should work by builder
---FILE--
-isValid(10));
-?>
---EXPECT--
-bool(false)
diff --git a/tests/integration/not_with_recursion.phpt b/tests/integration/not_with_recursion.phpt
deleted file mode 100644
index 9ffedd80..00000000
--- a/tests/integration/not_with_recursion.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---FILE--
-positive()
- )
- )
- )
- )
-);
-
-exceptionMessage(static fn() => $validator->assert(2));
-exceptionFullMessage(static fn() => $validator->assert(2));
-?>
---EXPECT--
-2 must not be an integer value
-- These rules must not pass for 2
- - 2 must not be an integer value
- - 2 must not be a positive number
\ No newline at end of file
diff --git a/tests/integration/not_without_recursion.phpt b/tests/integration/not_without_recursion.phpt
deleted file mode 100644
index 4b35633e..00000000
--- a/tests/integration/not_without_recursion.phpt
+++ /dev/null
@@ -1,16 +0,0 @@
---FILE--
-positive()
- );
- $validator->assert(2);
-});
-?>
---EXPECT--
-2 must not be an integer value
\ No newline at end of file
diff --git a/tests/integration/readme/custom_messages.phpt b/tests/integration/readme/custom_messages.phpt
deleted file mode 100644
index d56a923a..00000000
--- a/tests/integration/readme/custom_messages.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::alnum()
- ->noWhitespace()
- ->length(v::between(1, 15))
- ->assert('really messed up screen#name', [
- 'alnum' => '{{name}} must contain only letters and digits',
- 'noWhitespace' => '{{name}} cannot contain spaces',
- 'length' => '{{name}} must not have more than 15 chars',
- ])
-);
-?>
---EXPECT--
-[
- '__root__' => 'All of the required rules must pass for "really messed up screen#name"',
- 'alnum' => '"really messed up screen#name" must contain only letters and digits',
- 'noWhitespace' => '"really messed up screen#name" cannot contain spaces',
- 'lengthBetween' => 'The length of "really messed up screen#name" must be between 1 and 15',
-]
diff --git a/tests/integration/readme/example_1.phpt b/tests/integration/readme/example_1.phpt
deleted file mode 100644
index 1f1bc6ee..00000000
--- a/tests/integration/readme/example_1.phpt
+++ /dev/null
@@ -1,22 +0,0 @@
---FILE--
-name = 'Alexandre';
-$user->birthdate = '1987-07-01';
-
-$userValidator = v::property('name', v::stringType()->length(v::between(1, 32)))
- ->property('birthdate', v::dateTimeDiff('years', v::greaterThanOrEqual(18)));
-
-$userValidator->assert($user);
-
-echo 'Nothing to fail';
-?>
---EXPECT--
-Nothing to fail
diff --git a/tests/integration/readme/example_2.phpt b/tests/integration/readme/example_2.phpt
deleted file mode 100644
index 34ae6d91..00000000
--- a/tests/integration/readme/example_2.phpt
+++ /dev/null
@@ -1,14 +0,0 @@
---FILE--
- v::alnum()->noWhitespace()->lengthBetween(1, 15)->assert('really messed up screen#name')
-);
-?>
---EXPECT--
-- All of the required rules must pass for "really messed up screen#name"
- - "really messed up screen#name" must contain only letters (a-z) and digits (0-9)
- - "really messed up screen#name" must not contain whitespaces
- - The length of "really messed up screen#name" must be between 1 and 15
\ No newline at end of file
diff --git a/tests/integration/readme/getting_messages_as_an_array.phpt b/tests/integration/readme/getting_messages_as_an_array.phpt
deleted file mode 100644
index d2b00b2c..00000000
--- a/tests/integration/readme/getting_messages_as_an_array.phpt
+++ /dev/null
@@ -1,16 +0,0 @@
---FILE--
- v::alnum()->noWhitespace()->lengthBetween(1, 15)->assert('really messed up screen#name')
-);
-?>
---EXPECT--
-[
- '__root__' => 'All of the required rules must pass for "really messed up screen#name"',
- 'alnum' => '"really messed up screen#name" must contain only letters (a-z) and digits (0-9)',
- 'noWhitespace' => '"really messed up screen#name" must not contain whitespaces',
- 'lengthBetween' => 'The length of "really messed up screen#name" must be between 1 and 15',
-]
\ No newline at end of file
diff --git a/tests/integration/rules/allOf.phpt b/tests/integration/rules/allOf.phpt
deleted file mode 100644
index e805cae1..00000000
--- a/tests/integration/rules/allOf.phpt
+++ /dev/null
@@ -1,73 +0,0 @@
---FILE--
- [v::allOf(v::intType(), v::negative()), '2'],
- 'Wrapped by "not"' => [v::not(v::allOf(v::intType(), v::positive())), 3],
- 'Wrapping "not"' => [v::allOf(v::not(v::intType()), v::greaterThan(2)), 4],
- 'With a single template' => [v::allOf(v::stringType(), v::arrayType()), 5, 'This is a single template'],
- 'With multiple templates' => [
- v::allOf(v::stringType(), v::uppercase()),
- 5,
- [
- '__root__' => 'Two things are wrong',
- 'stringType' => 'Template for "stringType"',
- 'uppercase' => 'Template for "uppercase"',
- ],
- ],
-]);
-?>
---EXPECT--
-Two rules
-⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"2" must be an integer
-- All of the required rules must pass for "2"
- - "2" must be an integer
- - "2" must be a negative number
-[
- '__root__' => 'All of the required rules must pass for "2"',
- 'intType' => '"2" must be an integer',
- 'negative' => '"2" must be a negative number',
-]
-
-Wrapped by "not"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-3 must not be an integer
-- These rules must not pass for 3
- - 3 must not be an integer
- - 3 must not be a positive number
-[
- '__root__' => 'These rules must not pass for 3',
- 'intType' => '3 must not be an integer',
- 'positive' => '3 must not be a positive number',
-]
-
-Wrapping "not"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-4 must not be an integer
-- 4 must not be an integer
-[
- 'notIntType' => '4 must not be an integer',
-]
-
-With a single template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-This is a single template
-- This is a single template
-[
- 'allOf' => 'This is a single template',
-]
-
-With multiple templates
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Template for "stringType"
-- Two things are wrong
- - Template for "stringType"
- - Template for "uppercase"
-[
- '__root__' => 'Two things are wrong',
- 'stringType' => 'Template for "stringType"',
- 'uppercase' => 'Template for "uppercase"',
-]
diff --git a/tests/integration/rules/alnum.phpt b/tests/integration/rules/alnum.phpt
deleted file mode 100644
index 76cc23d9..00000000
--- a/tests/integration/rules/alnum.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::alnum()->assert('abc%1'));
-exceptionMessage(static fn() => v::alnum(' ')->assert('abc%2'));
-exceptionMessage(static fn() => v::not(v::alnum())->assert('abcd3'));
-exceptionMessage(static fn() => v::not(v::alnum('% '))->assert('abc%4'));
-exceptionFullMessage(static fn() => v::alnum()->assert('abc^1'));
-exceptionFullMessage(static fn() => v::not(v::alnum())->assert('abcd2'));
-exceptionFullMessage(static fn() => v::alnum('* &%')->assert('abc^3'));
-exceptionFullMessage(static fn() => v::not(v::alnum('^'))->assert('abc^4'));
-?>
---EXPECT--
-"abc%1" must contain only letters (a-z) and digits (0-9)
-"abc%2" must contain only letters (a-z), digits (0-9), and " "
-"abcd3" must not contain letters (a-z) or digits (0-9)
-"abc%4" must not contain letters (a-z), digits (0-9), or "% "
-- "abc^1" must contain only letters (a-z) and digits (0-9)
-- "abcd2" must not contain letters (a-z) or digits (0-9)
-- "abc^3" must contain only letters (a-z), digits (0-9), and "* &%"
-- "abc^4" must not contain letters (a-z), digits (0-9), or "^"
diff --git a/tests/integration/rules/alpha.phpt b/tests/integration/rules/alpha.phpt
deleted file mode 100644
index 83650702..00000000
--- a/tests/integration/rules/alpha.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::alpha()->assert('aaa%a'));
-exceptionMessage(static fn() => v::alpha(' ')->assert('bbb%b'));
-exceptionMessage(static fn() => v::not(v::alpha())->assert('ccccc'));
-exceptionMessage(static fn() => v::not(v::alpha('% '))->assert('ddd%d'));
-exceptionFullMessage(static fn() => v::alpha()->assert('eee^e'));
-exceptionFullMessage(static fn() => v::not(v::alpha())->assert('fffff'));
-exceptionFullMessage(static fn() => v::alpha('* &%')->assert('ggg^g'));
-exceptionFullMessage(static fn() => v::not(v::alpha('^'))->assert('hhh^h'));
-?>
---EXPECT--
-"aaa%a" must contain only letters (a-z)
-"bbb%b" must contain only letters (a-z) and " "
-"ccccc" must not contain letters (a-z)
-"ddd%d" must not contain letters (a-z) or "% "
-- "eee^e" must contain only letters (a-z)
-- "fffff" must not contain letters (a-z)
-- "ggg^g" must contain only letters (a-z) and "* &%"
-- "hhh^h" must not contain letters (a-z) or "^"
diff --git a/tests/integration/rules/alwaysInvalid.phpt b/tests/integration/rules/alwaysInvalid.phpt
deleted file mode 100644
index f5835138..00000000
--- a/tests/integration/rules/alwaysInvalid.phpt
+++ /dev/null
@@ -1,11 +0,0 @@
---FILE--
- v::alwaysInvalid()->assert('whatever'));
-exceptionFullMessage(static fn() => v::alwaysInvalid()->assert(''));
-?>
---EXPECT--
-"whatever" must be valid
-- "" must be valid
\ No newline at end of file
diff --git a/tests/integration/rules/alwaysValid.phpt b/tests/integration/rules/alwaysValid.phpt
deleted file mode 100644
index 1badfc0a..00000000
--- a/tests/integration/rules/alwaysValid.phpt
+++ /dev/null
@@ -1,11 +0,0 @@
---FILE--
- v::not(v::alwaysValid())->assert(true));
-exceptionFullMessage(static fn() => v::not(v::alwaysValid())->assert(true));
-?>
---EXPECT--
-`true` must be invalid
-- `true` must be invalid
\ No newline at end of file
diff --git a/tests/integration/rules/arrayType.phpt b/tests/integration/rules/arrayType.phpt
deleted file mode 100644
index fbd7304a..00000000
--- a/tests/integration/rules/arrayType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::arrayType()->assert('teste'));
-exceptionMessage(static fn() => v::not(v::arrayType())->assert([]));
-exceptionFullMessage(static fn() => v::arrayType()->assert(new ArrayObject()));
-exceptionFullMessage(static fn() => v::not(v::arrayType())->assert([1, 2, 3]));
-?>
---EXPECT--
-"teste" must be an array
-`[]` must not be an array
-- `ArrayObject { getArrayCopy() => [] }` must be an array
-- `[1, 2, 3]` must not be an array
\ No newline at end of file
diff --git a/tests/integration/rules/arrayVal.phpt b/tests/integration/rules/arrayVal.phpt
deleted file mode 100644
index ef419fe9..00000000
--- a/tests/integration/rules/arrayVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::arrayVal()->assert('Bla %123'));
-exceptionMessage(static fn() => v::not(v::arrayVal())->assert([42]));
-exceptionFullMessage(static fn() => v::arrayVal()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::arrayVal())->assert(new ArrayObject([2, 3])));
-?>
---EXPECT--
-"Bla %123" must be an array value
-`[42]` must not be an array value
-- `stdClass {}` must be an array value
-- `ArrayObject { getArrayCopy() => [2, 3] }` must not be an array value
diff --git a/tests/integration/rules/attributes.phpt b/tests/integration/rules/attributes.phpt
deleted file mode 100644
index 236d1aeb..00000000
--- a/tests/integration/rules/attributes.phpt
+++ /dev/null
@@ -1,78 +0,0 @@
---FILE--
- [v::attributes(), new WithAttributes('', 'john.doe@gmail.com', '2024-06-23')],
- 'Inverted' => [v::attributes(), new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', '+1234567890')],
- 'Not an object' => [v::attributes(), []],
- 'Nullable' => [v::attributes(), new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', 'not a phone number')],
- 'Multiple attributes, all failed' => [v::attributes(), new WithAttributes('', 'not an email', 'not a date', 'not a phone number')],
- 'Multiple attributes, one failed' => [v::attributes(), new WithAttributes('John Doe', 'john.doe@gmail.com', '22 years ago')],
-]);
-?>
---EXPECTF--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-name must not be empty
-- name must not be empty
-[
- 'name' => 'name must not be empty',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-phone must be a valid telephone number or must be null
-- phone must be a valid telephone number or must be null
-[
- 'phone' => 'phone must be a valid telephone number or must be null',
-]
-
-Not an object
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-`[]` must be an object
-- `[]` must be an object
-[
- 'attributes' => '`[]` must be an object',
-]
-
-Nullable
-⎺⎺⎺⎺⎺⎺⎺⎺
-phone must be a valid telephone number or must be null
-- phone must be a valid telephone number or must be null
-[
- 'phone' => 'phone must be a valid telephone number or must be null',
-]
-
-Multiple attributes, all failed
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-name must not be empty
-- All of the required rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }`
- - name must not be empty
- - email must be a valid email address
- - All of the required rules must pass for birthdate
- - birthdate must be a valid date in the format "%d-%d-%d"
- - For comparison with now, birthdate must be a valid datetime
- - phone must be a valid telephone number or must be null
-[
- '__root__' => 'All of the required rules must pass for `Respect\\Validation\\Test\\Stubs\\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }`',
- 'name' => 'name must not be empty',
- 'email' => 'email must be a valid email address',
- 'birthdate' => [
- '__root__' => 'All of the required rules must pass for birthdate',
- 'date' => 'birthdate must be a valid date in the format "%d-%d-%d"',
- 'dateTimeDiffLessThanOrEqual' => 'For comparison with now, birthdate must be a valid datetime',
- ],
- 'phone' => 'phone must be a valid telephone number or must be null',
-]
-
-Multiple attributes, one failed
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-birthdate must be a valid date in the format "%d-%d-%d"
-- birthdate must be a valid date in the format "%d-%d-%d"
-[
- 'birthdate' => 'birthdate must be a valid date in the format "%d-%d-%d"',
-]
diff --git a/tests/integration/rules/base.phpt b/tests/integration/rules/base.phpt
deleted file mode 100644
index 3e0e4fed..00000000
--- a/tests/integration/rules/base.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::base(61)->assert('Z01xSsg5675hic20dj'));
-exceptionFullMessage(static fn() => v::base(2)->assert(''));
-exceptionMessage(static fn() => v::not(v::base(2))->assert('011010001'));
-exceptionFullMessage(static fn() => v::not(v::base(2))->assert('011010001'));
-?>
---EXPECT--
-"Z01xSsg5675hic20dj" must be a number in base 61
-- "" must be a number in base 2
-"011010001" must not be a number in base 2
-- "011010001" must not be a number in base 2
\ No newline at end of file
diff --git a/tests/integration/rules/base64.phpt b/tests/integration/rules/base64.phpt
deleted file mode 100644
index 4dc709d6..00000000
--- a/tests/integration/rules/base64.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::base64()->assert('=c3VyZS4'));
-exceptionMessage(static fn() => v::not(v::base64())->assert('c3VyZS4='));
-exceptionFullMessage(static fn() => v::base64()->assert('=c3VyZS4'));
-exceptionFullMessage(static fn() => v::not(v::base64())->assert('c3VyZS4='));
-?>
---EXPECT--
-"=c3VyZS4" must be a base64 encoded string
-"c3VyZS4=" must not be a base64 encoded string
-- "=c3VyZS4" must be a base64 encoded string
-- "c3VyZS4=" must not be a base64 encoded string
\ No newline at end of file
diff --git a/tests/integration/rules/beetwen.phpt b/tests/integration/rules/beetwen.phpt
deleted file mode 100644
index 1ce71dc3..00000000
--- a/tests/integration/rules/beetwen.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- v::between(1, 2)->assert(0));
-exceptionMessage(static fn() => v::not(v::between('yesterday', 'tomorrow'))->assert('today'));
-exceptionFullMessage(static fn() => v::between('a', 'c')->assert('d'));
-exceptionFullMessage(static fn() => v::not(v::between(-INF, INF))->assert(0));
-exceptionFullMessage(static fn() => v::not(v::between('a', 'b'))->assert('a'));
-exceptionFullMessage(static fn() => v::not(v::between(1, 42))->assert(41));
-?>
---EXPECT--
-0 must be between 1 and 2
-"today" must not be between "yesterday" and "tomorrow"
-- "d" must be between "a" and "c"
-- 0 must not be between `-INF` and `INF`
-- "a" must not be between "a" and "b"
-- 41 must not be between 1 and 42
diff --git a/tests/integration/rules/betweenExclusive.phpt b/tests/integration/rules/betweenExclusive.phpt
deleted file mode 100644
index cc1199c1..00000000
--- a/tests/integration/rules/betweenExclusive.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---FILE--
- [v::betweenExclusive(1, 10), 12],
- 'Inverted' => [v::not(v::betweenExclusive(1, 10)), 5],
- 'With template' => [v::betweenExclusive(1, 10), 12, 'Bewildered bees buzzed between blooming begonias'],
- 'With name' => [v::betweenExclusive(1, 10)->setName('Range'), 10],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-12 must be greater than 1 and less than 10
-- 12 must be greater than 1 and less than 10
-[
- 'betweenExclusive' => '12 must be greater than 1 and less than 10',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-5 must not be greater than 1 or less than 10
-- 5 must not be greater than 1 or less than 10
-[
- 'notBetweenExclusive' => '5 must not be greater than 1 or less than 10',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Bewildered bees buzzed between blooming begonias
-- Bewildered bees buzzed between blooming begonias
-[
- 'betweenExclusive' => 'Bewildered bees buzzed between blooming begonias',
-]
-
-With name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Range must be greater than 1 and less than 10
-- Range must be greater than 1 and less than 10
-[
- 'betweenExclusive' => 'Range must be greater than 1 and less than 10',
-]
diff --git a/tests/integration/rules/boolType.phpt b/tests/integration/rules/boolType.phpt
deleted file mode 100644
index c21523e0..00000000
--- a/tests/integration/rules/boolType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::boolType()->assert('teste'));
-exceptionMessage(static fn() => v::not(v::boolType())->assert(true));
-exceptionFullMessage(static fn() => v::boolType()->assert([]));
-exceptionFullMessage(static fn() => v::not(v::boolType())->assert(false));
-?>
---EXPECT--
-"teste" must be a boolean
-`true` must not be a boolean
-- `[]` must be a boolean
-- `false` must not be a boolean
\ No newline at end of file
diff --git a/tests/integration/rules/boolVal.phpt b/tests/integration/rules/boolVal.phpt
deleted file mode 100644
index 7fd74ca0..00000000
--- a/tests/integration/rules/boolVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::boolVal()->assert('ok'));
-exceptionMessage(static fn() => v::not(v::boolVal())->assert('yes'));
-exceptionFullMessage(static fn() => v::boolVal()->assert('yep'));
-exceptionFullMessage(static fn() => v::not(v::boolVal())->assert('on'));
-?>
---EXPECT--
-"ok" must be a boolean value
-"yes" must not be a boolean value
-- "yep" must be a boolean value
-- "on" must not be a boolean value
diff --git a/tests/integration/rules/bsn.phpt b/tests/integration/rules/bsn.phpt
deleted file mode 100644
index fdf7690f..00000000
--- a/tests/integration/rules/bsn.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::bsn()->assert('acb'));
-exceptionMessage(static fn() => v::not(v::bsn())->assert('612890053'));
-exceptionFullMessage(static fn() => v::bsn()->assert('abc'));
-exceptionFullMessage(static fn() => v::not(v::bsn())->assert('612890053'));
-?>
---EXPECT--
-"acb" must be a valid BSN
-"612890053" must not be a valid BSN
-- "abc" must be a valid BSN
-- "612890053" must not be a valid BSN
\ No newline at end of file
diff --git a/tests/integration/rules/call.phpt b/tests/integration/rules/call.phpt
deleted file mode 100644
index 19e52c58..00000000
--- a/tests/integration/rules/call.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- v::call('trim', v::noWhitespace())->assert(' two words '));
-exceptionMessage(static fn() => v::not(v::call('stripslashes', v::stringType()))->assert(' some\\thing '));
-exceptionMessage(static fn() => v::call('stripslashes', v::alwaysValid())->assert([]));
-exceptionFullMessage(static fn() => v::call('strval', v::intType())->assert(1234));
-exceptionFullMessage(static fn() => v::not(v::call('is_float', v::boolType()))->assert(1.2));
-exceptionFullMessage(static fn() => v::call('array_shift', v::alwaysValid())->assert(INF));
-?>
---EXPECT--
-"two words" must not contain whitespaces
-" something " must not be a string
-`[]` must be a suitable argument for `stripslashes(string $string): string`
-- "1234" must be an integer
-- `true` must not be a boolean
-- `INF` must be a suitable argument for `array_shift(array &$array): ?mixed`
\ No newline at end of file
diff --git a/tests/integration/rules/callableType.phpt b/tests/integration/rules/callableType.phpt
deleted file mode 100644
index 9e6b8f65..00000000
--- a/tests/integration/rules/callableType.phpt
+++ /dev/null
@@ -1,17 +0,0 @@
---FILE--
- v::callableType()->assert([]));
-exceptionMessage(static fn() => v::not(v::callableType())->assert('trim'));
-exceptionFullMessage(static fn() => v::callableType()->assert(true));
-exceptionFullMessage(static fn() => v::not(v::callableType())->assert(static function (): void {
- // Do nothing
-}));
-?>
---EXPECT--
-`[]` must be a callable
-`trim(string $string, string $characters = " \n\r\t\u000b\u0000"): string` must not be a callable
-- `true` must be a callable
-- `function (): void` must not be a callable
\ No newline at end of file
diff --git a/tests/integration/rules/callback.phpt b/tests/integration/rules/callback.phpt
deleted file mode 100644
index 17e8305c..00000000
--- a/tests/integration/rules/callback.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::callback('is_string')->assert([]));
-exceptionMessage(static fn() => v::not(v::callback('is_string'))->assert('foo'));
-exceptionFullMessage(static fn() => v::callback('is_string')->assert(true));
-exceptionFullMessage(static fn() => v::not(v::callback('is_string'))->assert('foo'));
-?>
---EXPECT--
-`[]` must be valid
-"foo" must be invalid
-- `true` must be valid
-- "foo" must be invalid
\ No newline at end of file
diff --git a/tests/integration/rules/charset.phpt b/tests/integration/rules/charset.phpt
deleted file mode 100644
index 2ea9833f..00000000
--- a/tests/integration/rules/charset.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::charset('ASCII')->assert('açaí'));
-exceptionMessage(static fn() => v::not(v::charset('UTF-8'))->assert('açaí'));
-exceptionFullMessage(static fn() => v::charset('ASCII')->assert('açaí'));
-exceptionFullMessage(static fn() => v::not(v::charset('UTF-8'))->assert('açaí'));
-?>
---EXPECT--
-"açaí" must only contain characters from the `["ASCII"]` charset
-"açaí" must not contain any characters from the `["UTF-8"]` charset
-- "açaí" must only contain characters from the `["ASCII"]` charset
-- "açaí" must not contain any characters from the `["UTF-8"]` charset
\ No newline at end of file
diff --git a/tests/integration/rules/cnh.phpt b/tests/integration/rules/cnh.phpt
deleted file mode 100644
index ae970041..00000000
--- a/tests/integration/rules/cnh.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::cnh()->assert('batman'));
-exceptionMessage(static fn() => v::not(v::cnh())->assert('02650306461'));
-exceptionFullMessage(static fn() => v::cnh()->assert('bruce wayne'));
-exceptionFullMessage(static fn() => v::not(v::cnh())->assert('02650306461'));
-?>
---EXPECT--
-"batman" must be a valid CNH number
-"02650306461" must not be a valid CNH number
-- "bruce wayne" must be a valid CNH number
-- "02650306461" must not be a valid CNH number
diff --git a/tests/integration/rules/cnpj.phpt b/tests/integration/rules/cnpj.phpt
deleted file mode 100644
index 1dead977..00000000
--- a/tests/integration/rules/cnpj.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::cnpj()->assert('não cnpj'));
-exceptionMessage(static fn() => v::not(v::cnpj())->assert('65.150.175/0001-20'));
-exceptionFullMessage(static fn() => v::cnpj()->assert('test'));
-exceptionFullMessage(static fn() => v::not(v::cnpj())->assert('65.150.175/0001-20'));
-?>
---EXPECT--
-"não cnpj" must be a valid CNPJ number
-"65.150.175/0001-20" must not be a valid CNPJ number
-- "test" must be a valid CNPJ number
-- "65.150.175/0001-20" must not be a valid CNPJ number
diff --git a/tests/integration/rules/cntrl.phpt b/tests/integration/rules/cntrl.phpt
deleted file mode 100644
index f9d2c688..00000000
--- a/tests/integration/rules/cntrl.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::control()->assert('16-50'));
-exceptionMessage(static fn() => v::control('16')->assert('16-50'));
-exceptionMessage(static fn() => v::not(v::control())->assert("\n"));
-exceptionMessage(static fn() => v::not(v::control('16'))->assert("16\n"));
-exceptionFullMessage(static fn() => v::control()->assert('Foo'));
-exceptionFullMessage(static fn() => v::control('Bar')->assert('Foo'));
-exceptionFullMessage(static fn() => v::not(v::control())->assert("\n"));
-exceptionFullMessage(static fn() => v::not(v::control('Bar'))->assert("Bar\n"));
-?>
---EXPECT--
-"16-50" must only contain control characters
-"16-50" must only contain control characters and "16"
-"\n" must not contain control characters
-"16\n" must not contain control characters or "16"
-- "Foo" must only contain control characters
-- "Foo" must only contain control characters and "Bar"
-- "\n" must not contain control characters
-- "Bar\n" must not contain control characters or "Bar"
\ No newline at end of file
diff --git a/tests/integration/rules/consecutive.phpt b/tests/integration/rules/consecutive.phpt
deleted file mode 100644
index 1d4fc0f5..00000000
--- a/tests/integration/rules/consecutive.phpt
+++ /dev/null
@@ -1,139 +0,0 @@
---FILE--
- [v::consecutive(v::alwaysValid(), v::trueVal()), false],
- 'Inverted' => [v::not(v::consecutive(v::alwaysValid(), v::trueVal())), true],
- 'Default with inverted failing rule' => [v::consecutive(v::alwaysValid(), v::not(v::trueVal())), true],
- 'With wrapped name, default' => [
- v::consecutive(v::alwaysValid(), v::trueVal()->setName('Wrapped'))->setName('Wrapper'),
- false,
- ],
- 'With wrapper name, default' => [
- v::consecutive(v::alwaysValid(), v::trueVal())->setName('Wrapper'),
- false,
- ],
- 'With the name set in the wrapped rule of an inverted failing rule' => [
- v::consecutive(v::alwaysValid(), v::not(v::trueVal()->setName('Wrapped'))->setName('Not'))->setName('Wrapper'),
- true,
- ],
- 'With the name set in an inverted failing rule' => [
- v::consecutive(v::alwaysValid(), v::not(v::trueVal())->setName('Not'))->setName('Wrapper'),
- true,
- ],
- 'With the name set in the "consecutive" that has an inverted failing rule' => [
- v::consecutive(v::alwaysValid(), v::not(v::trueVal()))->setName('Wrapper'),
- true,
- ],
- 'With template' => [
- v::consecutive(v::alwaysValid(), v::trueVal()),
- false,
- 'Consecutive cool cats cunningly continuous cookies',
- ],
- 'With multiple templates' => [
- v::consecutive(v::alwaysValid(), v::trueVal()),
- false,
- ['trueVal' => 'Clever clowns craft consecutive clever clocks'],
- ],
- 'Real example' => [
- v::consecutive(
- v::key('countyCode', v::countryCode()),
- v::lazy(static fn($input) => v::key('subdivisionCode', v::subdivisionCode($input['countyCode']))),
- ),
- [
- 'countyCode' => 'BR',
- 'subdivisionCode' => 'CA',
- ],
- ],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-`false` must evaluate to `true`
-- `false` must evaluate to `true`
-[
- 'trueVal' => '`false` must evaluate to `true`',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-`true` must not evaluate to `true`
-- `true` must not evaluate to `true`
-[
- 'notTrueVal' => '`true` must not evaluate to `true`',
-]
-
-Default with inverted failing rule
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-`true` must not evaluate to `true`
-- `true` must not evaluate to `true`
-[
- 'notTrueVal' => '`true` must not evaluate to `true`',
-]
-
-With wrapped name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must evaluate to `true`
-- Wrapped must evaluate to `true`
-[
- 'trueVal' => 'Wrapped must evaluate to `true`',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must evaluate to `true`
-- Wrapper must evaluate to `true`
-[
- 'trueVal' => 'Wrapper must evaluate to `true`',
-]
-
-With the name set in the wrapped rule of an inverted failing rule
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not evaluate to `true`
-- Wrapped must not evaluate to `true`
-[
- 'notTrueVal' => 'Wrapped must not evaluate to `true`',
-]
-
-With the name set in an inverted failing rule
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not must not evaluate to `true`
-- Not must not evaluate to `true`
-[
- 'notTrueVal' => 'Not must not evaluate to `true`',
-]
-
-With the name set in the "consecutive" that has an inverted failing rule
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must not evaluate to `true`
-- Wrapper must not evaluate to `true`
-[
- 'notTrueVal' => 'Wrapper must not evaluate to `true`',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Consecutive cool cats cunningly continuous cookies
-- Consecutive cool cats cunningly continuous cookies
-[
- 'trueVal' => 'Consecutive cool cats cunningly continuous cookies',
-]
-
-With multiple templates
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Clever clowns craft consecutive clever clocks
-- Clever clowns craft consecutive clever clocks
-[
- 'trueVal' => 'Clever clowns craft consecutive clever clocks',
-]
-
-Real example
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-subdivisionCode must be a subdivision code of Brazil
-- subdivisionCode must be a subdivision code of Brazil
-[
- 'subdivisionCode' => 'subdivisionCode must be a subdivision code of Brazil',
-]
diff --git a/tests/integration/rules/consonant.phpt b/tests/integration/rules/consonant.phpt
deleted file mode 100644
index 06fedeac..00000000
--- a/tests/integration/rules/consonant.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::consonant()->assert('aeiou'));
-exceptionMessage(static fn() => v::consonant('d')->assert('daeiou'));
-exceptionMessage(static fn() => v::not(v::consonant())->assert('bcd'));
-exceptionMessage(static fn() => v::not(v::consonant('a'))->assert('abcd'));
-exceptionFullMessage(static fn() => v::consonant()->assert('aeiou'));
-exceptionFullMessage(static fn() => v::consonant('d')->assert('daeiou'));
-exceptionFullMessage(static fn() => v::not(v::consonant())->assert('bcd'));
-exceptionFullMessage(static fn() => v::not(v::consonant('a'))->assert('abcd'));
-?>
---EXPECT--
-"aeiou" must only contain consonants
-"daeiou" must only contain consonants and "d"
-"bcd" must not contain consonants
-"abcd" must not contain consonants or "a"
-- "aeiou" must only contain consonants
-- "daeiou" must only contain consonants and "d"
-- "bcd" must not contain consonants
-- "abcd" must not contain consonants or "a"
\ No newline at end of file
diff --git a/tests/integration/rules/contains.phpt b/tests/integration/rules/contains.phpt
deleted file mode 100644
index 81b337c6..00000000
--- a/tests/integration/rules/contains.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::contains('foo')->assert('bar'));
-exceptionMessage(static fn() => v::not(v::contains('foo'))->assert('fool'));
-exceptionFullMessage(static fn() => v::contains('foo')->assert(['bar']));
-exceptionFullMessage(static fn() => v::not(v::contains('foo', true))->assert(['bar', 'foo']));
-?>
---EXPECT--
-"bar" must contain "foo"
-"fool" must not contain "foo"
-- `["bar"]` must contain "foo"
-- `["bar", "foo"]` must not contain "foo"
\ No newline at end of file
diff --git a/tests/integration/rules/containsAny.phpt b/tests/integration/rules/containsAny.phpt
deleted file mode 100644
index afd5f417..00000000
--- a/tests/integration/rules/containsAny.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::containsAny(['foo', 'bar'])->assert('baz'));
-exceptionMessage(static fn() => v::not(v::containsAny(['foo', 'bar']))->assert('fool'));
-exceptionFullMessage(static fn() => v::containsAny(['foo', 'bar'])->assert(['baz']));
-exceptionFullMessage(static fn() => v::not(v::containsAny(['foo', 'bar'], true))->assert(['bar', 'foo']));
-?>
---EXPECT--
-"baz" must contain at least one value from `["foo", "bar"]`
-"fool" must not contain any value from `["foo", "bar"]`
-- `["baz"]` must contain at least one value from `["foo", "bar"]`
-- `["bar", "foo"]` must not contain any value from `["foo", "bar"]`
\ No newline at end of file
diff --git a/tests/integration/rules/countable.phpt b/tests/integration/rules/countable.phpt
deleted file mode 100644
index be27edfc..00000000
--- a/tests/integration/rules/countable.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::countable()->assert(1.0));
-exceptionMessage(static fn() => v::not(v::countable())->assert([]));
-exceptionFullMessage(static fn() => v::countable()->assert('Not countable!'));
-exceptionFullMessage(static fn() => v::not(v::countable())->assert(new ArrayObject()));
-?>
---EXPECT--
-1.0 must be a countable value
-`[]` must not be a countable value
-- "Not countable!" must be a countable value
-- `ArrayObject { getArrayCopy() => [] }` must not be a countable value
\ No newline at end of file
diff --git a/tests/integration/rules/countryCode.phpt b/tests/integration/rules/countryCode.phpt
deleted file mode 100644
index 25d1f806..00000000
--- a/tests/integration/rules/countryCode.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::countryCode()->assert('1'));
-exceptionMessage(static fn() => v::not(v::countryCode())->assert('BR'));
-exceptionFullMessage(static fn() => v::countryCode()->assert('1'));
-exceptionFullMessage(static fn() => v::not(v::countryCode())->assert('BR'));
-?>
---EXPECT--
-"1" must be a valid country code
-"BR" must not be a valid country code
-- "1" must be a valid country code
-- "BR" must not be a valid country code
\ No newline at end of file
diff --git a/tests/integration/rules/cpf.phpt b/tests/integration/rules/cpf.phpt
deleted file mode 100644
index 8927f50e..00000000
--- a/tests/integration/rules/cpf.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::cpf()->assert('this thing'));
-exceptionMessage(static fn() => v::not(v::cpf())->assert('276.865.775-11'));
-exceptionFullMessage(static fn() => v::cpf()->assert('your mother'));
-exceptionFullMessage(static fn() => v::not(v::cpf())->assert('61836182848'));
-?>
---EXPECT--
-"this thing" must be a valid CPF number
-"276.865.775-11" must not be a valid CPF number
-- "your mother" must be a valid CPF number
-- "61836182848" must not be a valid CPF number
diff --git a/tests/integration/rules/creditCard.phpt b/tests/integration/rules/creditCard.phpt
deleted file mode 100644
index d367b915..00000000
--- a/tests/integration/rules/creditCard.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::creditCard('Discover')->assert(3566002020360505));
-exceptionMessage(static fn() => v::not(v::creditCard('Visa'))->assert(4024007153361885));
-exceptionFullMessage(static fn() => v::creditCard('MasterCard')->assert(3566002020360505));
-exceptionFullMessage(static fn() => v::not(v::creditCard())->assert(5555444433331111));
-?>
---EXPECT--
-3566002020360505 must be a valid Discover credit card number
-4024007153361885 must not be a valid Visa credit card number
-- 3566002020360505 must be a valid MasterCard credit card number
-- 5555444433331111 must not be a valid credit card number
diff --git a/tests/integration/rules/currencyCode.phpt b/tests/integration/rules/currencyCode.phpt
deleted file mode 100644
index 6c6ee36f..00000000
--- a/tests/integration/rules/currencyCode.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::currencyCode()->assert('batman'));
-exceptionMessage(static fn() => v::not(v::currencyCode())->assert('BRL'));
-exceptionFullMessage(static fn() => v::currencyCode()->assert('ppz'));
-exceptionFullMessage(static fn() => v::not(v::currencyCode())->assert('GBP'));
-?>
---EXPECT--
-"batman" must be a valid currency code
-"BRL" must not be a valid currency code
-- "ppz" must be a valid currency code
-- "GBP" must not be a valid currency code
\ No newline at end of file
diff --git a/tests/integration/rules/date.phpt b/tests/integration/rules/date.phpt
deleted file mode 100644
index 2561363f..00000000
--- a/tests/integration/rules/date.phpt
+++ /dev/null
@@ -1,17 +0,0 @@
---FILE--
- v::date()->assert('2018-01-29T08:32:54+00:00'));
-exceptionMessage(static fn() => v::not(v::date())->assert('2018-01-29'));
-exceptionFullMessage(static fn() => v::date()->assert('2018-01-29T08:32:54+00:00'));
-exceptionFullMessage(static fn() => v::not(v::date('d/m/Y'))->assert('29/01/2018'));
-?>
---EXPECT--
-"2018-01-29T08:32:54+00:00" must be a valid date in the format "2005-12-30"
-"2018-01-29" must not be a valid date in the format "2005-12-30"
-- "2018-01-29T08:32:54+00:00" must be a valid date in the format "2005-12-30"
-- "29/01/2018" must not be a valid date in the format "30/12/2005"
diff --git a/tests/integration/rules/dateTime.phpt b/tests/integration/rules/dateTime.phpt
deleted file mode 100644
index 4981bc21..00000000
--- a/tests/integration/rules/dateTime.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---FILE--
- v::dateTime()->assert('FooBarBazz'));
-exceptionMessage(static fn() => v::dateTime('c')->assert('06-12-1995'));
-exceptionFullMessage(static fn() => v::dateTime()->assert('QuxQuuxx'));
-exceptionFullMessage(static fn() => v::dateTime('r')->assert(2018013030));
-exceptionMessage(static fn() => v::not(v::dateTime())->assert('4 days ago'));
-exceptionMessage(static fn() => v::not(v::dateTime('Y-m-d'))->assert('1988-09-09'));
-exceptionFullMessage(static fn() => v::not(v::dateTime())->assert('+3 weeks'));
-exceptionFullMessage(static fn() => v::not(v::dateTime('d/m/y'))->assert('23/07/99'));
-?>
---EXPECT--
-"FooBarBazz" must be a valid date/time
-"06-12-1995" must be a valid date/time in the format "2005-12-30T01:02:03+00:00"
-- "QuxQuuxx" must be a valid date/time
-- 2018013030 must be a valid date/time in the format "Fri, 30 Dec 2005 01:02:03 +0000"
-"4 days ago" must not be a valid date/time
-"1988-09-09" must not be a valid date/time in the format "2005-12-30"
-- "+3 weeks" must not be a valid date/time
-- "23/07/99" must not be a valid date/time in the format "30/12/05"
diff --git a/tests/integration/rules/dateTimeDiff.phpt b/tests/integration/rules/dateTimeDiff.phpt
deleted file mode 100644
index 7048353d..00000000
--- a/tests/integration/rules/dateTimeDiff.phpt
+++ /dev/null
@@ -1,189 +0,0 @@
---FILE--
- [v::dateTimeDiff('years', v::equals(2)), '1 year ago'],
- 'With $type = "months"' => [v::dateTimeDiff('months', v::equals(3)), '2 months ago'],
- 'With $type = "days"' => [v::dateTimeDiff('days', v::equals(4)), '3 days ago'],
- 'With $type = "hours"' => [v::dateTimeDiff('hours', v::equals(5)), '4 hours ago'],
- 'With $type = "minutes"' => [v::dateTimeDiff('minutes', v::equals(6)), '5 minutes ago'],
- 'With $type = "microseconds"' => [v::dateTimeDiff('microseconds', v::equals(7)), '6 microseconds ago'],
- 'With custom $format' => [v::dateTimeDiff('years', v::lessThan(8), 'd/m/Y'), '09/12/1988'],
- 'With input in non-parseable date' => [v::dateTimeDiff('years', v::equals(2)), 'not a date'],
- 'With input in incorrect $format' => [v::dateTimeDiff('years', v::equals(2), 'Y-m-d'), '1 year ago'],
- 'With custom $now' => [v::dateTimeDiff('years', v::lessThan(9), null, new DateTimeImmutable()), '09/12/1988'],
- 'With custom template' => [v::dateTimeDiff('years', v::equals(2)->setTemplate('Custom template')), '1 year ago'],
- 'Wrapped by "not"' => [v::not(v::dateTimeDiff('years', v::lessThan(8))), '7 year ago'],
- 'Wrapping "not"' => [v::dateTimeDiff('years', v::not(v::lessThan(9))), '8 year ago'],
- 'Wrapped with custom template' => [
- v::dateTimeDiff('years', v::equals(2)->setTemplate('Wrapped with custom template')),
- '1 year ago',
- ],
- 'Wrapper with custom template' => [
- v::dateTimeDiff('years', v::equals(2))->setTemplate('Wrapper with custom template'),
- '1 year ago',
- ],
- 'Without subsequent result' => [
- v::dateTimeDiff('years', v::primeNumber()->between(2, 5)),
- '1 year ago',
- ],
- 'Without subsequent result with templates' => [
- v::dateTimeDiff('years', v::primeNumber()->between(2, 5)),
- '1 year ago',
- [
- 'dateTimeDiff' => [
- 'primeNumber' => 'Interval must be a valid prime number',
- 'between' => 'Interval must be between 2 and 5',
- ],
- ],
- ],
-]);
-?>
---EXPECTF--
-With $type = "years"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between now and 1 year ago must be equal to 2
-- The number of years between now and 1 year ago must be equal to 2
-[
- 'dateTimeDiffEquals' => 'The number of years between now and 1 year ago must be equal to 2',
-]
-
-With $type = "months"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of months between now and 2 months ago must be equal to 3
-- The number of months between now and 2 months ago must be equal to 3
-[
- 'dateTimeDiffEquals' => 'The number of months between now and 2 months ago must be equal to 3',
-]
-
-With $type = "days"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of days between now and 3 days ago must be equal to 4
-- The number of days between now and 3 days ago must be equal to 4
-[
- 'dateTimeDiffEquals' => 'The number of days between now and 3 days ago must be equal to 4',
-]
-
-With $type = "hours"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of hours between now and 4 hours ago must be equal to 5
-- The number of hours between now and 4 hours ago must be equal to 5
-[
- 'dateTimeDiffEquals' => 'The number of hours between now and 4 hours ago must be equal to 5',
-]
-
-With $type = "minutes"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of minutes between now and 5 minutes ago must be equal to 6
-- The number of minutes between now and 5 minutes ago must be equal to 6
-[
- 'dateTimeDiffEquals' => 'The number of minutes between now and 5 minutes ago must be equal to 6',
-]
-
-With $type = "microseconds"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of microseconds between now and 6 microseconds ago must be equal to 7
-- The number of microseconds between now and 6 microseconds ago must be equal to 7
-[
- 'dateTimeDiffEquals' => 'The number of microseconds between now and 6 microseconds ago must be equal to 7',
-]
-
-With custom $format
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between %d/%d/%d and %d/%d/%d must be less than 8
-- The number of years between %d/%d/%d and %d/%d/%d must be less than 8
-[
- 'dateTimeDiffLessThan' => 'The number of years between %d/%d/%d and %d/%d/%d must be less than 8',
-]
-
-With input in non-parseable date
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-For comparison with now, "not a date" must be a valid datetime
-- For comparison with now, "not a date" must be a valid datetime
-[
- 'dateTimeDiffEquals' => 'For comparison with now, "not a date" must be a valid datetime',
-]
-
-With input in incorrect $format
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-For comparison with %d-%d-%d, "1 year ago" must be a valid datetime in the format %d-%d-%d
-- For comparison with %d-%d-%d, "1 year ago" must be a valid datetime in the format %d-%d-%d
-[
- 'dateTimeDiffEquals' => 'For comparison with %d-%d-%d, "1 year ago" must be a valid datetime in the format %d-%d-%d',
-]
-
-With custom $now
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between %d-%d-%d %d:%d:%d.%d and %d/%d/%d must be less than 9
-- The number of years between %d-%d-%d %d:%d:%d.%d and %d/%d/%d must be less than 9
-[
- 'dateTimeDiffLessThan' => 'The number of years between %d-%d-%d %d:%d:%d.%d and %d/%d/%d must be less than 9',
-]
-
-With custom template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Custom template
-- Custom template
-[
- 'equals' => 'Custom template',
-]
-
-Wrapped by "not"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between now and 7 year ago must not be less than 8
-- The number of years between now and 7 year ago must not be less than 8
-[
- 'notDateTimeDiffLessThan' => 'The number of years between now and 7 year ago must not be less than 8',
-]
-
-Wrapping "not"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between now and 8 year ago must not be less than 9
-- The number of years between now and 8 year ago must not be less than 9
-[
- 'dateTimeDiffNotLessThan' => 'The number of years between now and 8 year ago must not be less than 9',
-]
-
-Wrapped with custom template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped with custom template
-- Wrapped with custom template
-[
- 'equals' => 'Wrapped with custom template',
-]
-
-Wrapper with custom template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper with custom template
-- Wrapper with custom template
-[
- 'dateTimeDiffEquals' => 'Wrapper with custom template',
-]
-
-Without subsequent result
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between now and 1 year ago must be a prime number
-- All of the required rules must pass for 1 year ago
- - The number of years between now and 1 year ago must be a prime number
- - The number of years between now and 1 year ago must be between 2 and 5
-[
- '__root__' => 'All of the required rules must pass for 1 year ago',
- 'dateTimeDiffPrimeNumber' => 'The number of years between now and 1 year ago must be a prime number',
- 'dateTimeDiffBetween' => 'The number of years between now and 1 year ago must be between 2 and 5',
-]
-
-Without subsequent result with templates
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The number of years between now and 1 year ago must be a prime number
-- All of the required rules must pass for 1 year ago
- - The number of years between now and 1 year ago must be a prime number
- - The number of years between now and 1 year ago must be between 2 and 5
-[
- '__root__' => 'All of the required rules must pass for 1 year ago',
- 'dateTimeDiffPrimeNumber' => 'The number of years between now and 1 year ago must be a prime number',
- 'dateTimeDiffBetween' => 'The number of years between now and 1 year ago must be between 2 and 5',
-]
diff --git a/tests/integration/rules/decimal.phpt b/tests/integration/rules/decimal.phpt
deleted file mode 100644
index d2110c03..00000000
--- a/tests/integration/rules/decimal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::decimal(3)->assert(0.1234));
-exceptionFullMessage(static fn() => v::decimal(2)->assert(0.123));
-exceptionMessage(static fn() => v::not(v::decimal(5))->assert(0.12345));
-exceptionFullMessage(static fn() => v::not(v::decimal(2))->assert(0.34));
-?>
---EXPECT--
-0.1234 must have 3 decimals
-- 0.123 must have 2 decimals
-0.12345 must not have 5 decimals
-- 0.34 must not have 2 decimals
diff --git a/tests/integration/rules/digit.phpt b/tests/integration/rules/digit.phpt
deleted file mode 100644
index 3b0cdef3..00000000
--- a/tests/integration/rules/digit.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::digit()->assert('abc'));
-exceptionMessage(static fn() => v::digit('-')->assert('a-b'));
-exceptionMessage(static fn() => v::not(v::digit())->assert('123'));
-exceptionMessage(static fn() => v::not(v::digit('-'))->assert('1-3'));
-exceptionFullMessage(static fn() => v::digit()->assert('abc'));
-exceptionFullMessage(static fn() => v::digit('-')->assert('a-b'));
-exceptionFullMessage(static fn() => v::not(v::digit())->assert('123'));
-exceptionFullMessage(static fn() => v::not(v::digit('-'))->assert('1-3'));
-?>
---EXPECT--
-"abc" must contain only digits (0-9)
-"a-b" must contain only digits (0-9) and "-"
-"123" must not contain digits (0-9)
-"1-3" must not contain digits (0-9) and "-"
-- "abc" must contain only digits (0-9)
-- "a-b" must contain only digits (0-9) and "-"
-- "123" must not contain digits (0-9)
-- "1-3" must not contain digits (0-9) and "-"
diff --git a/tests/integration/rules/directory.phpt b/tests/integration/rules/directory.phpt
deleted file mode 100644
index c4d0a8fd..00000000
--- a/tests/integration/rules/directory.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::directory()->assert('batman'));
-exceptionMessage(static fn() => v::not(v::directory())->assert(dirname('/etc/')));
-exceptionFullMessage(static fn() => v::directory()->assert('ppz'));
-exceptionFullMessage(static fn() => v::not(v::directory())->assert(dirname('/etc/')));
-?>
---EXPECT--
-"batman" must be a directory
-"/" must not be a directory
-- "ppz" must be a directory
-- "/" must not be a directory
diff --git a/tests/integration/rules/domain.phpt b/tests/integration/rules/domain.phpt
deleted file mode 100644
index 351f3c8a..00000000
--- a/tests/integration/rules/domain.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::domain()->assert('batman'));
-exceptionMessage(static fn() => v::not(v::domain())->assert('r--w.com'));
-exceptionFullMessage(static fn() => v::domain()->assert('p-éz-.kk'));
-exceptionFullMessage(static fn() => v::not(v::domain())->assert('github.com'));
-?>
---EXPECT--
-"batman" must be a valid domain
-"r--w.com" must not be a valid domain
-- "p-éz-.kk" must be a valid domain
-- "github.com" must not be a valid domain
diff --git a/tests/integration/rules/each.phpt b/tests/integration/rules/each.phpt
deleted file mode 100644
index 179c2cdb..00000000
--- a/tests/integration/rules/each.phpt
+++ /dev/null
@@ -1,256 +0,0 @@
---FILE--
- [v::each(v::intType()), $nonIterable],
- 'Empty' => [v::each(v::intType()), $empty],
- 'Default' => [v::each(v::intType()), $default],
- 'Inverted' => [v::not(v::each(v::intType())), $inverted],
-
- // With name
- 'With name, non-iterable' => [v::each(v::intType()->setName('Wrapped'))->setName('Wrapper'), $nonIterable],
- 'With name, empty' => [v::each(v::intType()->setName('Wrapped'))->setName('Wrapper'), $empty],
- 'With name, default' => [v::each(v::intType()->setName('Wrapped'))->setName('Wrapper'), $default],
- 'With name, inverted' => [
- v::not(v::each(v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not'),
- $inverted,
- ],
- 'With wrapper name, default' => [v::each(v::intType())->setName('Wrapper'), $default],
- 'With wrapper name, inverted' => [
- v::not(v::each(v::intType())->setName('Wrapper'))->setName('Not'),
- $inverted,
- ],
- 'With Not name, inverted' => [
- v::not(v::each(v::intType()))->setName('Not'),
- $inverted,
- ],
-
- // With template
- 'With template, non-iterable' => [v::each(v::intType()), $nonIterable, 'You should have passed an iterable'],
- 'With template, empty' => [v::each(v::intType()), $empty, 'You should have passed an non-empty'],
- 'With template, default' => [v::each(v::intType()), $default, 'All items should have been integers'],
- 'with template, inverted' => [v::not(v::each(v::intType())), $inverted, 'All items should not have been integers'],
-
- // With array template
- 'With array template, default' => [
- v::each(v::intType()),
- $default, [
- 'each' => [
- '__root__' => 'Here a sequence of items that did not pass the validation',
- 'intType.1' => 'First item should have been an integer',
- 'intType.2' => 'Second item should have been an integer',
- 'intType.3' => 'Third item should have been an integer',
- ],
- ],
- ],
- 'With array template and name, default' => [
- v::each(v::intType()->setName('Wrapped'))->setName('Wrapper'),
- $default, [
- 'Wrapped' => [
- '__root__' => 'Here a sequence of items that did not pass the validation',
- 'Wrapped.1' => 'First item should have been an integer',
- 'Wrapped.2' => 'Second item should have been an integer',
- 'Wrapped.3' => 'Third item should have been an integer',
- ],
- ],
- ],
-]);
-?>
---EXPECT--
-Non-iterable
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-`null` must be iterable
-- `null` must be iterable
-[
- 'each' => '`null` must be iterable',
-]
-
-Empty
-⎺⎺⎺⎺⎺
-The value must not be empty
-- The value must not be empty
-[
- 'each' => 'The value must not be empty',
-]
-
-Default
-⎺⎺⎺⎺⎺⎺⎺
-"a" must be an integer
-- Each item in `["a", "b", "c"]` must be valid
- - "a" must be an integer
- - "b" must be an integer
- - "c" must be an integer
-[
- '__root__' => 'Each item in `["a", "b", "c"]` must be valid',
- 'intType.1' => '"a" must be an integer',
- 'intType.2' => '"b" must be an integer',
- 'intType.3' => '"c" must be an integer',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-1 must not be an integer
-- Each item in `[1, 2, 3]` must be invalid
- - 1 must not be an integer
- - 2 must not be an integer
- - 3 must not be an integer
-[
- '__root__' => 'Each item in `[1, 2, 3]` must be invalid',
- 'intType.1' => '1 must not be an integer',
- 'intType.2' => '2 must not be an integer',
- 'intType.3' => '3 must not be an integer',
-]
-
-With name, non-iterable
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be iterable
-- Wrapped must be iterable
-[
- 'Wrapped' => 'Wrapped must be iterable',
-]
-
-With name, empty
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be empty
-- Wrapped must not be empty
-[
- 'Wrapped' => 'Wrapped must not be empty',
-]
-
-With name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be an integer
-- Each item in Wrapped must be valid
- - Wrapped must be an integer
- - Wrapped must be an integer
- - Wrapped must be an integer
-[
- '__root__' => 'Each item in Wrapped must be valid',
- 'intType.1' => 'Wrapped must be an integer',
- 'intType.2' => 'Wrapped must be an integer',
- 'intType.3' => 'Wrapped must be an integer',
-]
-
-With name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be an integer
-- Each item in Wrapped must be invalid
- - Wrapped must not be an integer
- - Wrapped must not be an integer
- - Wrapped must not be an integer
-[
- '__root__' => 'Each item in Wrapped must be invalid',
- 'intType.1' => 'Wrapped must not be an integer',
- 'intType.2' => 'Wrapped must not be an integer',
- 'intType.3' => 'Wrapped must not be an integer',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must be an integer
-- Each item in Wrapper must be valid
- - Wrapper must be an integer
- - Wrapper must be an integer
- - Wrapper must be an integer
-[
- '__root__' => 'Each item in Wrapper must be valid',
- 'intType.1' => 'Wrapper must be an integer',
- 'intType.2' => 'Wrapper must be an integer',
- 'intType.3' => 'Wrapper must be an integer',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must not be an integer
-- Each item in Wrapper must be invalid
- - Wrapper must not be an integer
- - Wrapper must not be an integer
- - Wrapper must not be an integer
-[
- '__root__' => 'Each item in Wrapper must be invalid',
- 'intType.1' => 'Wrapper must not be an integer',
- 'intType.2' => 'Wrapper must not be an integer',
- 'intType.3' => 'Wrapper must not be an integer',
-]
-
-With Not name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not must not be an integer
-- Each item in Not must be invalid
- - Not must not be an integer
- - Not must not be an integer
- - Not must not be an integer
-[
- '__root__' => 'Each item in Not must be invalid',
- 'intType.1' => 'Not must not be an integer',
- 'intType.2' => 'Not must not be an integer',
- 'intType.3' => 'Not must not be an integer',
-]
-
-With template, non-iterable
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-You should have passed an iterable
-- You should have passed an iterable
-[
- 'each' => 'You should have passed an iterable',
-]
-
-With template, empty
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-You should have passed an non-empty
-- You should have passed an non-empty
-[
- 'each' => 'You should have passed an non-empty',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-All items should have been integers
-- All items should have been integers
-[
- 'each' => 'All items should have been integers',
-]
-
-with template, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-All items should not have been integers
-- All items should not have been integers
-[
- 'notEach' => 'All items should not have been integers',
-]
-
-With array template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-First item should have been an integer
-- Here a sequence of items that did not pass the validation
- - First item should have been an integer
- - Second item should have been an integer
- - Third item should have been an integer
-[
- '__root__' => 'Here a sequence of items that did not pass the validation',
- 'intType.1' => 'First item should have been an integer',
- 'intType.2' => 'Second item should have been an integer',
- 'intType.3' => 'Third item should have been an integer',
-]
-
-With array template and name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be an integer
-- Each item in Wrapped must be valid
- - Wrapped must be an integer
- - Wrapped must be an integer
- - Wrapped must be an integer
-[
- '__root__' => 'Each item in Wrapped must be valid',
- 'intType.1' => 'Wrapped must be an integer',
- 'intType.2' => 'Wrapped must be an integer',
- 'intType.3' => 'Wrapped must be an integer',
-]
diff --git a/tests/integration/rules/email.phpt b/tests/integration/rules/email.phpt
deleted file mode 100644
index f2e6552d..00000000
--- a/tests/integration/rules/email.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::email()->assert('batman'));
-exceptionMessage(static fn() => v::not(v::email())->assert('bruce.wayne@gothancity.com'));
-exceptionFullMessage(static fn() => v::email()->assert('bruce wayne'));
-exceptionFullMessage(static fn() => v::not(v::email())->assert('iambatman@gothancity.com'));
-?>
---EXPECT--
-"batman" must be a valid email address
-"bruce.wayne@gothancity.com" must not be an email address
-- "bruce wayne" must be a valid email address
-- "iambatman@gothancity.com" must not be an email address
\ No newline at end of file
diff --git a/tests/integration/rules/endsWith.phpt b/tests/integration/rules/endsWith.phpt
deleted file mode 100644
index 92d8d452..00000000
--- a/tests/integration/rules/endsWith.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::endsWith('foo')->assert('bar'));
-exceptionMessage(static fn() => v::not(v::endsWith('foo'))->assert(['bar', 'foo']));
-exceptionFullMessage(static fn() => v::endsWith('foo')->assert(''));
-exceptionFullMessage(static fn() => v::not(v::endsWith('foo'))->assert(['bar', 'foo']));
-?>
---EXPECT--
-"bar" must end with "foo"
-`["bar", "foo"]` must not end with "foo"
-- "" must end with "foo"
-- `["bar", "foo"]` must not end with "foo"
diff --git a/tests/integration/rules/equals.phpt b/tests/integration/rules/equals.phpt
deleted file mode 100644
index 8da4cc83..00000000
--- a/tests/integration/rules/equals.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::equals(123)->assert(321));
-exceptionMessage(static fn() => v::not(v::equals(321))->assert(321));
-exceptionFullMessage(static fn() => v::equals(123)->assert(321));
-exceptionFullMessage(static fn() => v::not(v::equals(321))->assert(321));
-?>
---EXPECT--
-321 must be equal to 123
-321 must not be equal to 321
-- 321 must be equal to 123
-- 321 must not be equal to 321
\ No newline at end of file
diff --git a/tests/integration/rules/equivalent.phpt b/tests/integration/rules/equivalent.phpt
deleted file mode 100644
index 5e1a628b..00000000
--- a/tests/integration/rules/equivalent.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::equivalent(true)->assert(false));
-exceptionMessage(static fn() => v::not(v::equivalent('Something'))->assert('someThing'));
-exceptionFullMessage(static fn() => v::equivalent(123)->assert('true'));
-exceptionFullMessage(static fn() => v::not(v::equivalent(true))->assert(1));
-?>
---EXPECT--
-`false` must be equivalent to `true`
-"someThing" must not be equivalent to "Something"
-- "true" must be equivalent to 123
-- 1 must not be equivalent to `true`
diff --git a/tests/integration/rules/even.phpt b/tests/integration/rules/even.phpt
deleted file mode 100644
index 9d0d934e..00000000
--- a/tests/integration/rules/even.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::even()->assert(-1));
-exceptionFullMessage(static fn() => v::even()->assert(5));
-exceptionMessage(static fn() => v::not(v::even())->assert(6));
-exceptionFullMessage(static fn() => v::not(v::even())->assert(8));
-?>
---EXPECT--
--1 must be an even number
-- 5 must be an even number
-6 must be an odd number
-- 8 must be an odd number
\ No newline at end of file
diff --git a/tests/integration/rules/executable.phpt b/tests/integration/rules/executable.phpt
deleted file mode 100644
index 377d480e..00000000
--- a/tests/integration/rules/executable.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::executable()->assert('bar'));
-exceptionMessage(static fn() => v::not(v::executable())->assert('tests/fixtures/executable'));
-exceptionFullMessage(static fn() => v::executable()->assert('bar'));
-exceptionFullMessage(static fn() => v::not(v::executable())->assert('tests/fixtures/executable'));
-?>
---EXPECT--
-"bar" must be an executable file
-"tests/fixtures/executable" must not be an executable file
-- "bar" must be an executable file
-- "tests/fixtures/executable" must not be an executable file
diff --git a/tests/integration/rules/exists.phpt b/tests/integration/rules/exists.phpt
deleted file mode 100644
index d7e44d1a..00000000
--- a/tests/integration/rules/exists.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::exists()->assert('/path/of/a/non-existent/file'));
-exceptionMessage(static fn() => v::not(v::exists())->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::exists()->assert('/path/of/a/non-existent/file'));
-exceptionFullMessage(static fn() => v::not(v::exists())->assert('tests/fixtures/valid-image.png'));
-?>
---EXPECT--
-"/path/of/a/non-existent/file" must be an existing file
-"tests/fixtures/valid-image.gif" must not be an existing file
-- "/path/of/a/non-existent/file" must be an existing file
-- "tests/fixtures/valid-image.png" must not be an existing file
\ No newline at end of file
diff --git a/tests/integration/rules/extension.phpt b/tests/integration/rules/extension.phpt
deleted file mode 100644
index 8328d4c2..00000000
--- a/tests/integration/rules/extension.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::extension('png')->assert('filename.txt'));
-exceptionMessage(static fn() => v::not(v::extension('gif'))->assert('filename.gif'));
-exceptionFullMessage(static fn() => v::extension('mp3')->assert('filename.wav'));
-exceptionFullMessage(static fn() => v::not(v::extension('png'))->assert('tests/fixtures/invalid-image.png'));
-?>
---EXPECT--
-"filename.txt" must have "png" extension
-"filename.gif" must not have "gif" extension
-- "filename.wav" must have "mp3" extension
-- "tests/fixtures/invalid-image.png" must not have "png" extension
diff --git a/tests/integration/rules/factor.phpt b/tests/integration/rules/factor.phpt
deleted file mode 100644
index 36a0566b..00000000
--- a/tests/integration/rules/factor.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::factor(3)->assert(2));
-exceptionMessage(static fn() => v::not(v::factor(0))->assert(300));
-exceptionFullMessage(static fn() => v::factor(5)->assert(3));
-exceptionFullMessage(static fn() => v::not(v::factor(6))->assert(1));
-?>
---EXPECT--
-2 must be a factor of 3
-300 must not be a factor of 0
-- 3 must be a factor of 5
-- 1 must not be a factor of 6
diff --git a/tests/integration/rules/falseVal.phpt b/tests/integration/rules/falseVal.phpt
deleted file mode 100644
index 9126cc37..00000000
--- a/tests/integration/rules/falseVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::falseVal()->assert(true));
-exceptionMessage(static fn() => v::not(v::falseVal())->assert('false'));
-exceptionFullMessage(static fn() => v::falseVal()->assert(1));
-exceptionFullMessage(static fn() => v::not(v::falseVal())->assert(0));
-?>
---EXPECT--
-`true` must evaluate to `false`
-"false" must not evaluate to `false`
-- 1 must evaluate to `false`
-- 0 must not evaluate to `false`
diff --git a/tests/integration/rules/fibonacci.phpt b/tests/integration/rules/fibonacci.phpt
deleted file mode 100644
index 3ea7fb3e..00000000
--- a/tests/integration/rules/fibonacci.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::fibonacci()->assert(4));
-exceptionMessage(static fn() => v::not(v::fibonacci())->assert(5));
-exceptionFullMessage(static fn() => v::fibonacci()->assert(16));
-exceptionFullMessage(static fn() => v::not(v::fibonacci())->assert(21));
-?>
---EXPECT--
-4 must be a valid Fibonacci number
-5 must not be a valid Fibonacci number
-- 16 must be a valid Fibonacci number
-- 21 must not be a valid Fibonacci number
diff --git a/tests/integration/rules/file.phpt b/tests/integration/rules/file.phpt
deleted file mode 100644
index be8f013c..00000000
--- a/tests/integration/rules/file.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::file()->assert('tests/fixtures/non-existent.sh'));
-exceptionMessage(static fn() => v::not(v::file())->assert('tests/fixtures/valid-image.png'));
-exceptionFullMessage(static fn() => v::file()->assert('tests/fixtures/non-existent.sh'));
-exceptionFullMessage(static fn() => v::not(v::file())->assert('tests/fixtures/valid-image.png'));
-?>
---EXPECT--
-"tests/fixtures/non-existent.sh" must be a valid file
-"tests/fixtures/valid-image.png" must be an invalid file
-- "tests/fixtures/non-existent.sh" must be a valid file
-- "tests/fixtures/valid-image.png" must be an invalid file
\ No newline at end of file
diff --git a/tests/integration/rules/filterVar.phpt b/tests/integration/rules/filterVar.phpt
deleted file mode 100644
index 45adebe3..00000000
--- a/tests/integration/rules/filterVar.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::filterVar(FILTER_VALIDATE_IP)->assert(42));
-exceptionMessage(static fn() => v::not(v::filterVar(FILTER_VALIDATE_BOOLEAN))->assert('On'));
-exceptionFullMessage(static fn() => v::filterVar(FILTER_VALIDATE_EMAIL)->assert(1.5));
-exceptionFullMessage(static fn() => v::not(v::filterVar(FILTER_VALIDATE_FLOAT))->assert(1.0));
-?>
---EXPECT--
-42 must be valid
-"On" must not be valid
-- 1.5 must be valid
-- 1.0 must not be valid
diff --git a/tests/integration/rules/finite.phpt b/tests/integration/rules/finite.phpt
deleted file mode 100644
index f893b9cc..00000000
--- a/tests/integration/rules/finite.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::finite()->assert(''));
-exceptionMessage(static fn() => v::not(v::finite())->assert(10));
-exceptionFullMessage(static fn() => v::finite()->assert([12]));
-exceptionFullMessage(static fn() => v::not(v::finite())->assert('123456'));
-?>
---EXPECT--
-"" must be a finite number
-10 must not be a finite number
-- `[12]` must be a finite number
-- "123456" must not be a finite number
diff --git a/tests/integration/rules/floatType.phpt b/tests/integration/rules/floatType.phpt
deleted file mode 100644
index 87b7e132..00000000
--- a/tests/integration/rules/floatType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::floatType()->assert('42.33'));
-exceptionMessage(static fn() => v::not(v::floatType())->assert(INF));
-exceptionFullMessage(static fn() => v::floatType()->assert(true));
-exceptionFullMessage(static fn() => v::not(v::floatType())->assert(2.0));
-?>
---EXPECT--
-"42.33" must be float
-`INF` must not be float
-- `true` must be float
-- 2.0 must not be float
\ No newline at end of file
diff --git a/tests/integration/rules/floatval.phpt b/tests/integration/rules/floatval.phpt
deleted file mode 100644
index 7d676174..00000000
--- a/tests/integration/rules/floatval.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::floatVal()->assert('a'));
-exceptionMessage(static fn() => v::not(v::floatVal())->assert(165.0));
-exceptionFullMessage(static fn() => v::floatVal()->assert('a'));
-exceptionFullMessage(static fn() => v::not(v::floatVal())->assert('165.7'));
-?>
---EXPECT--
-"a" must be a float value
-165.0 must not be a float value
-- "a" must be a float value
-- "165.7" must not be a float value
\ No newline at end of file
diff --git a/tests/integration/rules/graph.phpt b/tests/integration/rules/graph.phpt
deleted file mode 100644
index 1bbb6a46..00000000
--- a/tests/integration/rules/graph.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::graph()->assert("foo\nbar"));
-exceptionMessage(static fn() => v::graph('foo')->assert("foo\nbar"));
-exceptionMessage(static fn() => v::not(v::graph())->assert('foobar'));
-exceptionMessage(static fn() => v::not(v::graph("\n"))->assert("foo\nbar"));
-exceptionFullMessage(static fn() => v::graph()->assert("foo\nbar"));
-exceptionFullMessage(static fn() => v::graph('foo')->assert("foo\nbar"));
-exceptionFullMessage(static fn() => v::not(v::graph())->assert('foobar'));
-exceptionFullMessage(static fn() => v::not(v::graph("\n"))->assert("foo\nbar"));
-?>
---EXPECT--
-"foo\nbar" must contain only graphical characters
-"foo\nbar" must contain only graphical characters and "foo"
-"foobar" must not contain graphical characters
-"foo\nbar" must not contain graphical characters or "\n"
-- "foo\nbar" must contain only graphical characters
-- "foo\nbar" must contain only graphical characters and "foo"
-- "foobar" must not contain graphical characters
-- "foo\nbar" must not contain graphical characters or "\n"
diff --git a/tests/integration/rules/greaterThan.phpt b/tests/integration/rules/greaterThan.phpt
deleted file mode 100644
index feb1a507..00000000
--- a/tests/integration/rules/greaterThan.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::greaterThan(21)->assert(12));
-exceptionMessage(static fn() => v::not(v::greaterThan('yesterday'))->assert('today'));
-exceptionFullMessage(static fn() => v::greaterThan('2018-09-09')->assert('1988-09-09'));
-exceptionFullMessage(static fn() => v::not(v::greaterThan('a'))->assert('ba'));
-?>
---EXPECT--
-12 must be greater than 21
-"today" must not be greater than "yesterday"
-- "1988-09-09" must be greater than "2018-09-09"
-- "ba" must not be greater than "a"
diff --git a/tests/integration/rules/greaterThanOrEqual.phpt b/tests/integration/rules/greaterThanOrEqual.phpt
deleted file mode 100644
index 66c47f63..00000000
--- a/tests/integration/rules/greaterThanOrEqual.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::greaterThanOrEqual(INF)->assert(10));
-exceptionMessage(static fn() => v::not(v::greaterThanOrEqual(5))->assert(INF));
-exceptionFullMessage(static fn() => v::greaterThanOrEqual('today')->assert('yesterday'));
-exceptionFullMessage(static fn() => v::not(v::greaterThanOrEqual('a'))->assert('z'));
-?>
---EXPECT--
-10 must be greater than or equal to `INF`
-`INF` must be less than 5
-- "yesterday" must be greater than or equal to "today"
-- "z" must be less than "a"
\ No newline at end of file
diff --git a/tests/integration/rules/hetu.phpt b/tests/integration/rules/hetu.phpt
deleted file mode 100644
index 82b97e88..00000000
--- a/tests/integration/rules/hetu.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---FILE--
- [v::hetu(), '010106A901O'],
- 'Inverted' => [v::not(v::hetu()), '010106A9012'],
- 'With template' => [v::hetu(), '010106A901O', 'That is not a HETU'],
- 'With name' => [v::hetu()->setName('Hetu'), '010106A901O'],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-"010106A901O" must be a valid Finnish personal identity code
-- "010106A901O" must be a valid Finnish personal identity code
-[
- 'hetu' => '"010106A901O" must be a valid Finnish personal identity code',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-"010106A9012" must not be a valid Finnish personal identity code
-- "010106A9012" must not be a valid Finnish personal identity code
-[
- 'notHetu' => '"010106A9012" must not be a valid Finnish personal identity code',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-That is not a HETU
-- That is not a HETU
-[
- 'hetu' => 'That is not a HETU',
-]
-
-With name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Hetu must be a valid Finnish personal identity code
-- Hetu must be a valid Finnish personal identity code
-[
- 'hetu' => 'Hetu must be a valid Finnish personal identity code',
-]
diff --git a/tests/integration/rules/hexRgbColor.phpt b/tests/integration/rules/hexRgbColor.phpt
deleted file mode 100644
index 52095816..00000000
--- a/tests/integration/rules/hexRgbColor.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::hexRgbColor()->assert('invalid'));
-exceptionMessage(static fn() => v::not(v::hexRgbColor())->assert('#808080'));
-exceptionFullMessage(static fn() => v::hexRgbColor()->assert('invalid'));
-exceptionFullMessage(static fn() => v::not(v::hexRgbColor())->assert('#808080'));
-?>
---EXPECT--
-"invalid" must be a hex RGB color
-"#808080" must not be a hex RGB color
-- "invalid" must be a hex RGB color
-- "#808080" must not be a hex RGB color
diff --git a/tests/integration/rules/iban.phpt b/tests/integration/rules/iban.phpt
deleted file mode 100644
index abd33a03..00000000
--- a/tests/integration/rules/iban.phpt
+++ /dev/null
@@ -1,20 +0,0 @@
---FILE--
- v::iban()->assert('SE35 5000 5880 7742'));
-exceptionMessage(static fn() => v::not(v::iban())->assert('GB82 WEST 1234 5698 7654 32'));
-exceptionFullMessage(static fn() => v::iban()->assert('NOT AN IBAN'));
-exceptionFullMessage(static fn() => v::not(v::iban())->assert('HU93 1160 0006 0000 0000 1234 5676'));?>
---SKIPIF--
-
---EXPECT--
-"SE35 5000 5880 7742" must be a valid IBAN
-"GB82 WEST 1234 5698 7654 32" must not be a valid IBAN
-- "NOT AN IBAN" must be a valid IBAN
-- "HU93 1160 0006 0000 0000 1234 5676" must not be a valid IBAN
diff --git a/tests/integration/rules/identical.phpt b/tests/integration/rules/identical.phpt
deleted file mode 100644
index 96ce4a2c..00000000
--- a/tests/integration/rules/identical.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::identical(123)->assert(321));
-exceptionMessage(static fn() => v::not(v::identical(321))->assert(321));
-exceptionFullMessage(static fn() => v::identical(123)->assert(321));
-exceptionFullMessage(static fn() => v::not(v::identical(321))->assert(321));
-?>
---EXPECT--
-321 must be identical to 123
-321 must not be identical to 321
-- 321 must be identical to 123
-- 321 must not be identical to 321
\ No newline at end of file
diff --git a/tests/integration/rules/image.phpt b/tests/integration/rules/image.phpt
deleted file mode 100644
index c2c501a6..00000000
--- a/tests/integration/rules/image.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::image()->assert('tests/fixtures/invalid-image.png'));
-exceptionMessage(static fn() => v::not(v::image())->assert('tests/fixtures/valid-image.png'));
-exceptionFullMessage(static fn() => v::image()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::image())->assert('tests/fixtures/valid-image.gif'));
-?>
---EXPECT--
-"tests/fixtures/invalid-image.png" must be a valid image file
-"tests/fixtures/valid-image.png" must not be a valid image file
-- `stdClass {}` must be a valid image file
-- "tests/fixtures/valid-image.gif" must not be a valid image file
\ No newline at end of file
diff --git a/tests/integration/rules/imei.phpt b/tests/integration/rules/imei.phpt
deleted file mode 100644
index b09d0127..00000000
--- a/tests/integration/rules/imei.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::imei()->assert('490154203237512'));
-exceptionMessage(static fn() => v::not(v::imei())->assert('350077523237513'));
-exceptionFullMessage(static fn() => v::imei()->assert(null));
-exceptionFullMessage(static fn() => v::not(v::imei())->assert('356938035643809'));
-?>
---EXPECT--
-"490154203237512" must be a valid IMEI number
-"350077523237513" must not be a valid IMEI number
-- `null` must be a valid IMEI number
-- "356938035643809" must not be a valid IMEI number
\ No newline at end of file
diff --git a/tests/integration/rules/in.phpt b/tests/integration/rules/in.phpt
deleted file mode 100644
index 672ad14e..00000000
--- a/tests/integration/rules/in.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::in([3, 2])->assert(1));
-exceptionMessage(static fn() => v::not(v::in('foobar'))->assert('foo'));
-exceptionFullMessage(static fn() => v::in([2, '1', 3], true)->assert('2'));
-exceptionFullMessage(static fn() => v::not(v::in([2, '1', 3], true))->assert('1'));
-?>
---EXPECT--
-1 must be in `[3, 2]`
-"foo" must not be in "foobar"
-- "2" must be in `[2, "1", 3]`
-- "1" must not be in `[2, "1", 3]`
diff --git a/tests/integration/rules/infinite.phpt b/tests/integration/rules/infinite.phpt
deleted file mode 100644
index 4d0be911..00000000
--- a/tests/integration/rules/infinite.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::infinite()->assert(-9));
-exceptionMessage(static fn() => v::not(v::infinite())->assert(INF));
-exceptionFullMessage(static fn() => v::infinite()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::infinite())->assert(INF * -1));
-?>
---EXPECT--
--9 must be an infinite number
-`INF` must not be an infinite number
-- `stdClass {}` must be an infinite number
-- `-INF` must not be an infinite number
diff --git a/tests/integration/rules/instance.phpt b/tests/integration/rules/instance.phpt
deleted file mode 100644
index 5fe6ae9f..00000000
--- a/tests/integration/rules/instance.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::instance(DateTime::class)->assert(''));
-exceptionMessage(static fn() => v::not(v::instance(Traversable::class))->assert(new ArrayObject()));
-exceptionFullMessage(static fn() => v::instance(ArrayIterator::class)->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::instance(stdClass::class))->assert(new stdClass()));
-?>
---EXPECT--
-"" must be an instance of `DateTime`
-`ArrayObject { getArrayCopy() => [] }` must not be an instance of `Traversable`
-- `stdClass {}` must be an instance of `ArrayIterator`
-- `stdClass {}` must not be an instance of `stdClass`
diff --git a/tests/integration/rules/intType.phpt b/tests/integration/rules/intType.phpt
deleted file mode 100644
index 92833a92..00000000
--- a/tests/integration/rules/intType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::intType()->assert(new stdClass()));
-exceptionMessage(static fn() => v::not(v::intType())->assert(42));
-exceptionFullMessage(static fn() => v::intType()->assert(INF));
-exceptionFullMessage(static fn() => v::not(v::intType())->assert(1234567890));
-?>
---EXPECT--
-`stdClass {}` must be an integer
-42 must not be an integer
-- `INF` must be an integer
-- 1234567890 must not be an integer
\ No newline at end of file
diff --git a/tests/integration/rules/intVal.phpt b/tests/integration/rules/intVal.phpt
deleted file mode 100644
index 8081b2d3..00000000
--- a/tests/integration/rules/intVal.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- v::intVal()->assert('42.33'));
-exceptionMessage(static fn() => v::not(v::intVal())->assert(2));
-exceptionFullMessage(static fn() => v::intVal()->assert('Foo'));
-exceptionFullMessage(static fn() => v::not(v::intVal())->assert(3));
-exceptionFullMessage(static fn() => v::not(v::intVal())->assert(-42));
-exceptionFullMessage(static fn() => v::not(v::intVal())->assert('-42'));
-?>
---EXPECT--
-"42.33" must be an integer value
-2 must not be an integer value
-- "Foo" must be an integer value
-- 3 must not be an integer value
-- -42 must not be an integer value
-- "-42" must not be an integer value
\ No newline at end of file
diff --git a/tests/integration/rules/ip.phpt b/tests/integration/rules/ip.phpt
deleted file mode 100644
index 95573535..00000000
--- a/tests/integration/rules/ip.phpt
+++ /dev/null
@@ -1,28 +0,0 @@
---FILE--
- v::ip()->assert('257.0.0.1'));
-exceptionMessage(static fn() => v::not(v::ip())->assert('127.0.0.1'));
-exceptionMessage(static fn() => v::ip('127.0.1.*')->assert('127.0.0.1'));
-exceptionMessage(static fn() => v::not(v::ip('127.0.1.*'))->assert('127.0.1.1'));
-exceptionFullMessage(static fn() => v::ip()->assert('257.0.0.1'));
-exceptionFullMessage(static fn() => v::not(v::ip())->assert('127.0.0.1'));
-exceptionFullMessage(static fn() => v::ip('127.0.1.*')->assert('127.0.0.1'));
-exceptionFullMessage(static fn() => v::not(v::ip('127.0.1.*'))->assert('127.0.1.1'));?>
---SKIPIF--
-
---EXPECT--
-"257.0.0.1" must be an IP address
-"127.0.0.1" must not be an IP address
-"127.0.0.1" must be an IP address in the 127.0.1.0-127.0.1.255 range
-"127.0.1.1" must not be an IP address in the 127.0.1.0-127.0.1.255 range
-- "257.0.0.1" must be an IP address
-- "127.0.0.1" must not be an IP address
-- "127.0.0.1" must be an IP address in the 127.0.1.0-127.0.1.255 range
-- "127.0.1.1" must not be an IP address in the 127.0.1.0-127.0.1.255 range
diff --git a/tests/integration/rules/isbn.phpt b/tests/integration/rules/isbn.phpt
deleted file mode 100644
index b01e8ad3..00000000
--- a/tests/integration/rules/isbn.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::isbn()->assert('ISBN-12: 978-0-596-52068-7'));
-exceptionMessage(static fn() => v::not(v::isbn())->assert('ISBN-13: 978-0-596-52068-7'));
-exceptionFullMessage(static fn() => v::isbn()->assert('978 10 596 52068 7'));
-exceptionFullMessage(static fn() => v::not(v::isbn())->assert('978 0 596 52068 7'));
-?>
---EXPECT--
-"ISBN-12: 978-0-596-52068-7" must be a valid ISBN
-"ISBN-13: 978-0-596-52068-7" must not be a valid ISBN
-- "978 10 596 52068 7" must be a valid ISBN
-- "978 0 596 52068 7" must not be a valid ISBN
\ No newline at end of file
diff --git a/tests/integration/rules/iterableType.phpt b/tests/integration/rules/iterableType.phpt
deleted file mode 100644
index 50713213..00000000
--- a/tests/integration/rules/iterableType.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---FILE--
- [v::iterableType(), null],
- 'Inverted' => [v::not(v::iterableType()), [1, 2, 3]],
- 'With template' => [v::iterableType(), null, 'Not an iterable at all'],
- 'With name' => [v::iterableType()->setName('Options'), null],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-`null` must be iterable
-- `null` must be iterable
-[
- 'iterableType' => '`null` must be iterable',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-`[1, 2, 3]` must not iterable
-- `[1, 2, 3]` must not iterable
-[
- 'notIterableType' => '`[1, 2, 3]` must not iterable',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not an iterable at all
-- Not an iterable at all
-[
- 'iterableType' => 'Not an iterable at all',
-]
-
-With name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Options must be iterable
-- Options must be iterable
-[
- 'iterableType' => 'Options must be iterable',
-]
diff --git a/tests/integration/rules/iterableVal.phpt b/tests/integration/rules/iterableVal.phpt
deleted file mode 100644
index 92567911..00000000
--- a/tests/integration/rules/iterableVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::iterableVal()->assert(3));
-exceptionMessage(static fn() => v::not(v::iterableVal())->assert([2, 3]));
-exceptionFullMessage(static fn() => v::iterableVal()->assert('String'));
-exceptionFullMessage(static fn() => v::not(v::iterableVal())->assert(new stdClass()));
-?>
---EXPECT--
-3 must be an iterable value
-`[2, 3]` must not be an iterable value
-- "String" must be an iterable value
-- `stdClass {}` must not be an iterable value
\ No newline at end of file
diff --git a/tests/integration/rules/json.phpt b/tests/integration/rules/json.phpt
deleted file mode 100644
index 8b4887d8..00000000
--- a/tests/integration/rules/json.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::json()->assert(false));
-exceptionMessage(static fn() => v::not(v::json())->assert('{"foo": "bar", "number":1}'));
-exceptionFullMessage(static fn() => v::json()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::json())->assert('{}'));
-?>
---EXPECT--
-`false` must be a valid JSON string
-"{\"foo\": \"bar\", \"number\":1}" must not be a valid JSON string
-- `stdClass {}` must be a valid JSON string
-- "{}" must not be a valid JSON string
diff --git a/tests/integration/rules/key.phpt b/tests/integration/rules/key.phpt
deleted file mode 100644
index 76f08bdc..00000000
--- a/tests/integration/rules/key.phpt
+++ /dev/null
@@ -1,154 +0,0 @@
---FILE--
- [v::key('foo', v::intType()), []],
- 'Default' => [v::key('foo', v::intType()), ['foo' => 'string']],
- 'Inverted' => [v::not(v::key('foo', v::intType())), ['foo' => 12]],
- 'Double-inverted with missing key' => [
- v::not(v::not(v::key('foo', v::intType()))),
- [],
- ],
-
- // With custom name
- 'With wrapped name, missing key' => [
- v::key('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'),
- [],
- ],
- 'With wrapped name, default' => [
- v::key('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'),
- ['foo' => 'string'],
- ],
- 'With wrapped name, inverted' => [
- v::not(v::key('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not'),
- ['foo' => 12],
- ],
- 'With wrapper name, default' => [
- v::key('foo', v::intType())->setName('Wrapper'),
- ['foo' => 'string'],
- ],
- 'With wrapper name, missing key' => [
- v::key('foo', v::intType())->setName('Wrapper'),
- [],
- ],
- 'With wrapper name, inverted' => [
- v::not(v::key('foo', v::intType())->setName('Wrapper'))->setName('Not'),
- ['foo' => 12],
- ],
- 'With "Not" name, inverted' => [
- v::not(v::key('foo', v::intType()))->setName('Not'),
- ['foo' => 12],
- ],
-
- // With custom template
- 'With template, default' => [v::key('foo', v::intType()), ['foo' => 'string'], 'That key is off-key'],
- 'With template, inverted' => [v::not(v::key('foo', v::intType())), ['foo' => 12], 'No off-key key'],
-]);
-?>
---EXPECT--
-Missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-Default
-⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-Double-inverted with missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-With wrapped name, missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be present
-- Wrapped must be present
-[
- 'foo' => 'Wrapped must be present',
-]
-
-With wrapped name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be an integer
-- Wrapped must be an integer
-[
- 'foo' => 'Wrapped must be an integer',
-]
-
-With wrapped name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be an integer
-- Wrapped must not be an integer
-[
- 'foo' => 'Wrapped must not be an integer',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-With wrapper name, missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With "Not" name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-That key is off-key
-- That key is off-key
-[
- 'foo' => 'That key is off-key',
-]
-
-With template, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-No off-key key
-- No off-key key
-[
- 'foo' => 'No off-key key',
-]
diff --git a/tests/integration/rules/keyExists.phpt b/tests/integration/rules/keyExists.phpt
deleted file mode 100644
index 63850411..00000000
--- a/tests/integration/rules/keyExists.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---FILE--
- [v::keyExists('foo'), ['bar' => 'baz']],
- 'Inverted mode' => [v::not(v::keyExists('foo')), ['foo' => 'baz']],
- 'Custom name' => [v::keyExists('foo')->setName('Custom name'), ['bar' => 'baz']],
- 'Custom template' => [v::keyExists('foo'), ['bar' => 'baz'], 'Custom template for `{{name}}`'],
-]);
-?>
---EXPECT--
-Default mode
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-Inverted mode
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be present
-- foo must not be present
-[
- 'foo' => 'foo must not be present',
-]
-
-Custom name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Custom name must be present
-- Custom name must be present
-[
- 'foo' => 'Custom name must be present',
-]
-
-Custom template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Custom template for `foo`
-- Custom template for `foo`
-[
- 'foo' => 'Custom template for `foo`',
-]
diff --git a/tests/integration/rules/keyOptional.phpt b/tests/integration/rules/keyOptional.phpt
deleted file mode 100644
index 4ce4753a..00000000
--- a/tests/integration/rules/keyOptional.phpt
+++ /dev/null
@@ -1,121 +0,0 @@
---FILE--
- [v::keyOptional('foo', v::intType()), ['foo' => 'string']],
- 'Inverted' => [v::not(v::keyOptional('foo', v::intType())), ['foo' => 12]],
- 'Inverted with missing key' => [
- v::not(v::keyOptional('foo', v::intType())),
- [],
- ],
-
- // With custom name
- 'With wrapped name, default' => [
- v::keyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'),
- ['foo' => 'string'],
- ],
- 'With wrapped name, inverted' => [
- v::not(v::keyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not'),
- ['foo' => 12],
- ],
- 'With wrapper name, default' => [
- v::keyOptional('foo', v::intType())->setName('Wrapper'),
- ['foo' => 'string'],
- ],
- 'With wrapper name, inverted' => [
- v::not(v::keyOptional('foo', v::intType())->setName('Wrapper'))->setName('Not'),
- ['foo' => 12],
- ],
- 'With "Not" name, inverted' => [
- v::not(v::keyOptional('foo', v::intType()))->setName('Not'),
- ['foo' => 12],
- ],
-
- // With custom template
- 'With template, default' => [v::keyOptional('foo', v::intType()), ['foo' => 'string'], 'That key is off-key'],
- 'With template, inverted' => [v::not(v::keyOptional('foo', v::intType())), ['foo' => 12], 'No off-key key'],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-Inverted with missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-With wrapped name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be an integer
-- Wrapped must be an integer
-[
- 'foo' => 'Wrapped must be an integer',
-]
-
-With wrapped name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be an integer
-- Wrapped must not be an integer
-[
- 'foo' => 'Wrapped must not be an integer',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With "Not" name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-That key is off-key
-- That key is off-key
-[
- 'foo' => 'That key is off-key',
-]
-
-With template, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-No off-key key
-- No off-key key
-[
- 'foo' => 'No off-key key',
-]
diff --git a/tests/integration/rules/keySet.phpt b/tests/integration/rules/keySet.phpt
deleted file mode 100644
index 7f273ecc..00000000
--- a/tests/integration/rules/keySet.phpt
+++ /dev/null
@@ -1,227 +0,0 @@
---FILE--
- [v::keySet(v::key('foo', v::intType())), ['foo' => 'string']],
- 'one rule / one missing key' => [v::keySet(v::keyExists('foo')), []],
- 'one rule / one extra key' => [v::keySet(v::keyExists('foo')), ['foo' => 42, 'bar' => 'string']],
- 'one rule / one extra key / one missing key' => [v::keySet(v::keyExists('foo')), ['bar' => true]],
- 'one rule / two extra keys' => [v::keySet(v::keyExists('foo')), ['foo' => 42, 'bar' => 'string', 'baz' => true]],
- 'one rule / more than ten extra keys' => [
- v::keySet(v::keyExists('foo')),
- [
- 'foo' => 42,
- 'bar' => 'string',
- 'baz' => true,
- 'qux' => false,
- 'quux' => 42,
- 'corge' => 'string',
- 'grault' => true,
- 'garply' => false,
- 'waldo' => 42,
- 'fred' => 'string',
- 'plugh' => true,
- 'xyzzy' => false,
- 'thud' => 42,
- ],
- ],
- 'multiple rules / one failed' => [
- v::keySet(v::keyExists('foo'), v::keyExists('bar')),
- ['foo' => 42],
- ],
- 'multiple rules / all failed' => [
- v::keySet(v::keyExists('foo'), v::keyExists('bar')),
- [],
- ],
- 'multiple rules / one extra key' => [
- v::keySet(v::keyExists('foo'), v::keyExists('bar')),
- ['foo' => 42, 'bar' => 'string', 'baz' => true],
- ],
- 'multiple rules / one extra key / one missing' => [
- v::keySet(
- v::keyExists('foo'),
- v::keyExists('bar')
- ),
- ['bar' => 'string', 'baz' => true],
- ],
- 'multiple rules / two extra keys' => [
- v::keySet(
- v::keyExists('foo'),
- v::keyExists('bar'),
- v::keyOptional('qux', v::intType())
- ),
- ['foo' => 42, 'bar' => 'string', 'baz' => true, 'qux' => false],
- ],
- 'multiple rules / all failed validation' => [
- v::keySet(
- v::key('foo', v::intType()),
- v::key('bar', v::intType()),
- v::key('baz', v::intType())
- ),
- ['foo' => 42, 'bar' => 'string', 'baz' => true],
- ],
- 'multiple rules / single missing key / single failed validation' => [
- v::keySet(
- v::create()
- ->key('foo', v::intType())
- ->key('bar', v::intType())
- ->key('baz', v::intType())
- ),
- ['foo' => 42, 'bar' => 'string'],
- ],
-]);
-?>
---EXPECT--
-one rule / one failed
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-one rule / one missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-one rule / one extra key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-bar must not be present
-- bar must not be present
-[
- 'bar' => 'bar must not be present',
-]
-
-one rule / one extra key / one missing key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- `["bar": true]` contains both missing and extra keys
- - foo must be present
- - bar must not be present
-[
- '__root__' => '`["bar": true]` contains both missing and extra keys',
- 'foo' => 'foo must be present',
- 'bar' => 'bar must not be present',
-]
-
-one rule / two extra keys
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-bar must not be present
-- `["foo": 42, "bar": "string", "baz": true]` contains extra keys
- - bar must not be present
- - baz must not be present
-[
- '__root__' => '`["foo": 42, "bar": "string", "baz": true]` contains extra keys',
- 'bar' => 'bar must not be present',
- 'baz' => 'baz must not be present',
-]
-
-one rule / more than ten extra keys
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-bar must not be present
-- `["foo": 42, "bar": "string", "baz": true, "qux": false, "quux": 42, ...]` contains extra keys
- - bar must not be present
- - baz must not be present
- - qux must not be present
- - quux must not be present
- - corge must not be present
- - grault must not be present
- - garply must not be present
- - waldo must not be present
- - fred must not be present
- - plugh must not be present
-[
- '__root__' => '`["foo": 42, "bar": "string", "baz": true, "qux": false, "quux": 42, ...]` contains extra keys',
- 'bar' => 'bar must not be present',
- 'baz' => 'baz must not be present',
- 'qux' => 'qux must not be present',
- 'quux' => 'quux must not be present',
- 'corge' => 'corge must not be present',
- 'grault' => 'grault must not be present',
- 'garply' => 'garply must not be present',
- 'waldo' => 'waldo must not be present',
- 'fred' => 'fred must not be present',
- 'plugh' => 'plugh must not be present',
-]
-
-multiple rules / one failed
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-bar must be present
-- bar must be present
-[
- 'bar' => 'bar must be present',
-]
-
-multiple rules / all failed
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- `[]` contains missing keys
- - foo must be present
- - bar must be present
-[
- '__root__' => '`[]` contains missing keys',
- 'foo' => 'foo must be present',
- 'bar' => 'bar must be present',
-]
-
-multiple rules / one extra key
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-baz must not be present
-- baz must not be present
-[
- 'baz' => 'baz must not be present',
-]
-
-multiple rules / one extra key / one missing
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- `["bar": "string", "baz": true]` contains both missing and extra keys
- - foo must be present
- - baz must not be present
-[
- '__root__' => '`["bar": "string", "baz": true]` contains both missing and extra keys',
- 'foo' => 'foo must be present',
- 'baz' => 'baz must not be present',
-]
-
-multiple rules / two extra keys
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-qux must be an integer
-- `["foo": 42, "bar": "string", "baz": true, "qux": false]` contains extra keys
- - qux must be an integer
- - baz must not be present
-[
- '__root__' => '`["foo": 42, "bar": "string", "baz": true, "qux": false]` contains extra keys',
- 'qux' => 'qux must be an integer',
- 'baz' => 'baz must not be present',
-]
-
-multiple rules / all failed validation
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-bar must be an integer
-- `["foo": 42, "bar": "string", "baz": true]` validation failed
- - bar must be an integer
- - baz must be an integer
-[
- '__root__' => '`["foo": 42, "bar": "string", "baz": true]` validation failed',
- 'bar' => 'bar must be an integer',
- 'baz' => 'baz must be an integer',
-]
-
-multiple rules / single missing key / single failed validation
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-bar must be an integer
-- `["foo": 42, "bar": "string"]` contains missing keys
- - bar must be an integer
- - baz must be present
-[
- '__root__' => '`["foo": 42, "bar": "string"]` contains missing keys',
- 'bar' => 'bar must be an integer',
- 'baz' => 'baz must be present',
-]
diff --git a/tests/integration/rules/languageCode.phpt b/tests/integration/rules/languageCode.phpt
deleted file mode 100644
index d5fd4421..00000000
--- a/tests/integration/rules/languageCode.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::languageCode()->assert(null));
-exceptionMessage(static fn() => v::not(v::languageCode())->assert('pt'));
-exceptionFullMessage(static fn() => v::languageCode()->assert('por'));
-exceptionFullMessage(static fn() => v::not(v::languageCode())->assert('en'));
-?>
---EXPECT--
-`null` must be a valid language code
-"pt" must not be a valid language code
-- "por" must be a valid language code
-- "en" must not be a valid language code
diff --git a/tests/integration/rules/lazy.phpt b/tests/integration/rules/lazy.phpt
deleted file mode 100644
index 4f8d580d..00000000
--- a/tests/integration/rules/lazy.phpt
+++ /dev/null
@@ -1,99 +0,0 @@
---FILE--
- [v::lazy(static fn() => v::intType()), true],
- 'Inverted' => [v::not(v::lazy(static fn() => v::intType())), 2],
- 'With created name, default' => [
- v::lazy(static fn() => v::intType()->setName('Created'))->setName('Wrapper'),
- true,
- ],
- 'With wrapper name, default' => [
- v::lazy(static fn() => v::intType())->setName('Wrapper'),
- true,
- ],
- 'With created name, inverted' => [
- v::not(v::lazy(static fn() => v::intType()->setName('Created'))->setName('Wrapped'))->setName('Not'),
- 2,
- ],
- 'With wrapper name, inverted' => [
- v::not(v::lazy(static fn() => v::intType())->setName('Wrapped'))->setName('Not'),
- 2,
- ],
- 'With not name, inverted' => [
- v::not(v::lazy(static fn() => v::intType()))->setName('Not'),
- 2,
- ],
- 'With template, default' => [
- v::lazy(static fn() => v::intType()),
- true,
- 'Lazy lizards lounging like lords in the local lagoon',
- ],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-`true` must be an integer
-- `true` must be an integer
-[
- 'intType' => '`true` must be an integer',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-2 must not be an integer
-- 2 must not be an integer
-[
- 'notIntType' => '2 must not be an integer',
-]
-
-With created name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Created must be an integer
-- Created must be an integer
-[
- 'intType' => 'Created must be an integer',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must be an integer
-- Wrapper must be an integer
-[
- 'intType' => 'Wrapper must be an integer',
-]
-
-With created name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Created must not be an integer
-- Created must not be an integer
-[
- 'notIntType' => 'Created must not be an integer',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be an integer
-- Wrapped must not be an integer
-[
- 'notIntType' => 'Wrapped must not be an integer',
-]
-
-With not name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not must not be an integer
-- Not must not be an integer
-[
- 'notIntType' => 'Not must not be an integer',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Lazy lizards lounging like lords in the local lagoon
-- Lazy lizards lounging like lords in the local lagoon
-[
- 'intType' => 'Lazy lizards lounging like lords in the local lagoon',
-]
diff --git a/tests/integration/rules/leapDate.phpt b/tests/integration/rules/leapDate.phpt
deleted file mode 100644
index 40964c74..00000000
--- a/tests/integration/rules/leapDate.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::leapDate('Y-m-d')->assert('1989-02-29'));
-exceptionMessage(static fn() => v::not(v::leapDate('Y-m-d'))->assert('1988-02-29'));
-exceptionFullMessage(static fn() => v::leapDate('Y-m-d')->assert('1990-02-29'));
-exceptionFullMessage(static fn() => v::not(v::leapDate('Y-m-d'))->assert('1992-02-29'));
-?>
---EXPECT--
-"1989-02-29" must be a valid leap date
-"1988-02-29" must not be a leap date
-- "1990-02-29" must be a valid leap date
-- "1992-02-29" must not be a leap date
\ No newline at end of file
diff --git a/tests/integration/rules/leapYear.phpt b/tests/integration/rules/leapYear.phpt
deleted file mode 100644
index c50e39cd..00000000
--- a/tests/integration/rules/leapYear.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::leapYear()->assert('2009'));
-exceptionMessage(static fn() => v::not(v::leapYear())->assert('2008'));
-exceptionFullMessage(static fn() => v::leapYear()->assert('2009-02-29'));
-exceptionFullMessage(static fn() => v::not(v::leapYear())->assert('2008'));
-?>
---EXPECT--
-"2009" must be a valid leap year
-"2008" must not be a leap year
-- "2009-02-29" must be a valid leap year
-- "2008" must not be a leap year
\ No newline at end of file
diff --git a/tests/integration/rules/length.phpt b/tests/integration/rules/length.phpt
deleted file mode 100644
index 4dd62130..00000000
--- a/tests/integration/rules/length.phpt
+++ /dev/null
@@ -1,53 +0,0 @@
---FILE--
- [v::length(v::equals(3)), 'tulip'],
- 'Inverted wrapped' => [v::length(v::not(v::equals(4))), 'rose'],
- 'Inverted wrapper' => [v::not(v::length(v::equals(4))), 'fern'],
- 'With template' => [v::length(v::equals(3)), 'azalea', 'This is a template'],
- 'With wrapper name' => [v::length(v::equals(3))->setName('Cactus'), 'peyote'],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-The length of "tulip" must be equal to 3
-- The length of "tulip" must be equal to 3
-[
- 'lengthEquals' => 'The length of "tulip" must be equal to 3',
-]
-
-Inverted wrapped
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The length of "rose" must not be equal to 4
-- The length of "rose" must not be equal to 4
-[
- 'lengthNotEquals' => 'The length of "rose" must not be equal to 4',
-]
-
-Inverted wrapper
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The length of "fern" must not be equal to 4
-- The length of "fern" must not be equal to 4
-[
- 'notLengthEquals' => 'The length of "fern" must not be equal to 4',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-This is a template
-- This is a template
-[
- 'lengthEquals' => 'This is a template',
-]
-
-With wrapper name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The length of Cactus must be equal to 3
-- The length of Cactus must be equal to 3
-[
- 'lengthEquals' => 'The length of Cactus must be equal to 3',
-]
diff --git a/tests/integration/rules/lessThan.phpt b/tests/integration/rules/lessThan.phpt
deleted file mode 100644
index 251fbc42..00000000
--- a/tests/integration/rules/lessThan.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::lessThan(12)->assert(21));
-exceptionMessage(static fn() => v::not(v::lessThan('today'))->assert('yesterday'));
-exceptionFullMessage(static fn() => v::lessThan('1988-09-09')->assert('2018-09-09'));
-exceptionFullMessage(static fn() => v::not(v::lessThan('b'))->assert('a'));
-?>
---EXPECT--
-21 must be less than 12
-"yesterday" must not be less than "today"
-- "2018-09-09" must be less than "1988-09-09"
-- "a" must not be less than "b"
diff --git a/tests/integration/rules/lessThanOrEqual.phpt b/tests/integration/rules/lessThanOrEqual.phpt
deleted file mode 100644
index 487437c3..00000000
--- a/tests/integration/rules/lessThanOrEqual.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::lessThanOrEqual(10)->assert(11));
-exceptionMessage(static fn() => v::not(v::lessThanOrEqual(10))->assert(5));
-exceptionFullMessage(static fn() => v::lessThanOrEqual('today')->assert('tomorrow'));
-exceptionFullMessage(static fn() => v::not(v::lessThanOrEqual('b'))->assert('a'));
-?>
---EXPECT--
-11 must be less than or equal to 10
-5 must be greater than 10
-- "tomorrow" must be less than or equal to "today"
-- "a" must be greater than "b"
\ No newline at end of file
diff --git a/tests/integration/rules/lowercase.phpt b/tests/integration/rules/lowercase.phpt
deleted file mode 100644
index c98ed352..00000000
--- a/tests/integration/rules/lowercase.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::lowercase()->assert('UPPERCASE'));
-exceptionMessage(static fn() => v::not(v::lowercase())->assert('lowercase'));
-exceptionFullMessage(static fn() => v::lowercase()->assert('UPPERCASE'));
-exceptionFullMessage(static fn() => v::not(v::lowercase())->assert('lowercase'));
-?>
---EXPECT--
-"UPPERCASE" must contain only lowercase letters
-"lowercase" must not contain only lowercase letters
-- "UPPERCASE" must contain only lowercase letters
-- "lowercase" must not contain only lowercase letters
\ No newline at end of file
diff --git a/tests/integration/rules/luhn.phpt b/tests/integration/rules/luhn.phpt
deleted file mode 100644
index 785267c6..00000000
--- a/tests/integration/rules/luhn.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::luhn()->assert('2222400041240021'));
-exceptionMessage(static fn() => v::not(v::luhn())->assert('2223000048400011'));
-exceptionFullMessage(static fn() => v::luhn()->assert('340316193809334'));
-exceptionFullMessage(static fn() => v::not(v::luhn())->assert('6011000990139424'));
-?>
---EXPECT--
-"2222400041240021" must be a valid Luhn number
-"2223000048400011" must not be a valid Luhn number
-- "340316193809334" must be a valid Luhn number
-- "6011000990139424" must not be a valid Luhn number
diff --git a/tests/integration/rules/macAddress.phpt b/tests/integration/rules/macAddress.phpt
deleted file mode 100644
index 40588b0f..00000000
--- a/tests/integration/rules/macAddress.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::macAddress()->assert('00-11222:33:44:55'));
-exceptionMessage(static fn() => v::not(v::macAddress())->assert('00:11:22:33:44:55'));
-exceptionFullMessage(static fn() => v::macAddress()->assert('90-bc-nk:1a-dd-cc'));
-exceptionFullMessage(static fn() => v::not(v::macAddress())->assert('AF:0F:bd:12:44:ba'));
-?>
---EXPECT--
-"00-11222:33:44:55" must be a valid MAC address
-"00:11:22:33:44:55" must not be a valid MAC address
-- "90-bc-nk:1a-dd-cc" must be a valid MAC address
-- "AF:0F:bd:12:44:ba" must not be a valid MAC address
diff --git a/tests/integration/rules/max.phpt b/tests/integration/rules/max.phpt
deleted file mode 100644
index 2c89e7df..00000000
--- a/tests/integration/rules/max.phpt
+++ /dev/null
@@ -1,96 +0,0 @@
---FILE--
- [v::max(v::negative()), $nonIterable],
- 'Empty' => [v::max(v::negative()), $empty],
- 'Default' => [v::max(v::negative()), $default],
- 'Inverted' => [v::not(v::max(v::negative())), $negative],
- 'With wrapped name, default' => [v::max(v::negative()->setName('Wrapped'))->setName('Wrapper'), $default],
- 'With wrapper name, default' => [v::max(v::negative())->setName('Wrapper'), $default],
- 'With wrapped name, inverted' => [v::not(v::max(v::negative()->setName('Wrapped')))->setName('Wrapper'), $negative],
- 'With wrapper name, inverted' => [v::not(v::max(v::negative()))->setName('Wrapper'), $negative],
- 'With template, default' => [v::max(v::negative()), $default, 'The maximum of the value is not what we expect'],
-]);
-?>
---EXPECT--
-Non-iterable
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-`null` must be iterable
-- `null` must be iterable
-[
- 'max' => '`null` must be iterable',
-]
-
-Empty
-⎺⎺⎺⎺⎺
-The value must not be empty
-- The value must not be empty
-[
- 'max' => 'The value must not be empty',
-]
-
-Default
-⎺⎺⎺⎺⎺⎺⎺
-As the maximum of `[1, 2, 3]`, 3 must be a negative number
-- As the maximum of `[1, 2, 3]`, 3 must be a negative number
-[
- 'maxNegative' => 'As the maximum of `[1, 2, 3]`, 3 must be a negative number',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-As the maximum of `[-3, -2, -1]`, -1 must not be a negative number
-- As the maximum of `[-3, -2, -1]`, -1 must not be a negative number
-[
- 'notMaxNegative' => 'As the maximum of `[-3, -2, -1]`, -1 must not be a negative number',
-]
-
-With wrapped name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The maximum of Wrapped must be a negative number
-- The maximum of Wrapped must be a negative number
-[
- 'maxNegative' => 'The maximum of Wrapped must be a negative number',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The maximum of Wrapper must be a negative number
-- The maximum of Wrapper must be a negative number
-[
- 'maxNegative' => 'The maximum of Wrapper must be a negative number',
-]
-
-With wrapped name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The maximum of Wrapped must not be a negative number
-- The maximum of Wrapped must not be a negative number
-[
- 'notMaxNegative' => 'The maximum of Wrapped must not be a negative number',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The maximum of Wrapper must not be a negative number
-- The maximum of Wrapper must not be a negative number
-[
- 'notMaxNegative' => 'The maximum of Wrapper must not be a negative number',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The maximum of the value is not what we expect
-- The maximum of the value is not what we expect
-[
- 'maxNegative' => 'The maximum of the value is not what we expect',
-]
diff --git a/tests/integration/rules/mimetype.phpt b/tests/integration/rules/mimetype.phpt
deleted file mode 100644
index 5ff1c636..00000000
--- a/tests/integration/rules/mimetype.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::mimetype('image/png')->assert('image.png'));
-exceptionMessage(static fn() => v::not(v::mimetype('image/png'))->assert('tests/fixtures/valid-image.png'));
-exceptionFullMessage(static fn() => v::mimetype('image/png')->assert('tests/fixtures/invalid-image.png'));
-exceptionFullMessage(static fn() => v::not(v::mimetype('image/png'))->assert('tests/fixtures/valid-image.png'));
-?>
---EXPECT--
-"image.png" must have the "image/png" MIME type
-"tests/fixtures/valid-image.png" must not have the "image/png" MIME type
-- "tests/fixtures/invalid-image.png" must have the "image/png" MIME type
-- "tests/fixtures/valid-image.png" must not have the "image/png" MIME type
\ No newline at end of file
diff --git a/tests/integration/rules/min.phpt b/tests/integration/rules/min.phpt
deleted file mode 100644
index 36ba95c1..00000000
--- a/tests/integration/rules/min.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---FILE--
- [v::min(v::equals(1)), [2, 3]],
- 'Inverted' => [v::not(v::min(v::equals(1))), [1, 2, 3]],
- 'With template' => [v::min(v::equals(1)), [2, 3], 'That did not go as planned'],
- 'With name' => [v::min(v::equals(1))->setName('Options'), [2, 3]],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-As the minimum from `[2, 3]`, 2 must be equal to 1
-- As the minimum from `[2, 3]`, 2 must be equal to 1
-[
- 'minEquals' => 'As the minimum from `[2, 3]`, 2 must be equal to 1',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-As the minimum from `[1, 2, 3]`, 1 must not be equal to 1
-- As the minimum from `[1, 2, 3]`, 1 must not be equal to 1
-[
- 'notMinEquals' => 'As the minimum from `[1, 2, 3]`, 1 must not be equal to 1',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-That did not go as planned
-- That did not go as planned
-[
- 'minEquals' => 'That did not go as planned',
-]
-
-With name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The minimum from Options must be equal to 1
-- The minimum from Options must be equal to 1
-[
- 'minEquals' => 'The minimum from Options must be equal to 1',
-]
diff --git a/tests/integration/rules/multiple.phpt b/tests/integration/rules/multiple.phpt
deleted file mode 100644
index d15f4992..00000000
--- a/tests/integration/rules/multiple.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::multiple(3)->assert(22));
-exceptionMessage(static fn() => v::not(v::multiple(3))->assert(9));
-exceptionFullMessage(static fn() => v::multiple(2)->assert(5));
-exceptionFullMessage(static fn() => v::not(v::multiple(5))->assert(25));
-?>
---EXPECT--
-22 must be a multiple of 3
-9 must not be a multiple of 3
-- 5 must be a multiple of 2
-- 25 must not be a multiple of 5
\ No newline at end of file
diff --git a/tests/integration/rules/negative.phpt b/tests/integration/rules/negative.phpt
deleted file mode 100644
index 019585df..00000000
--- a/tests/integration/rules/negative.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::negative()->assert(16));
-exceptionMessage(static fn() => v::not(v::negative())->assert(-10));
-exceptionFullMessage(static fn() => v::negative()->assert('a'));
-exceptionFullMessage(static fn() => v::not(v::negative())->assert('-144'));
-?>
---EXPECT--
-16 must be a negative number
--10 must not be a negative number
-- "a" must be a negative number
-- "-144" must not be a negative number
\ No newline at end of file
diff --git a/tests/integration/rules/nfeAccessKey.phpt b/tests/integration/rules/nfeAccessKey.phpt
deleted file mode 100644
index ffeaf701..00000000
--- a/tests/integration/rules/nfeAccessKey.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::nfeAccessKey()->assert('31841136830118868211870485416765268625116906'));
-exceptionMessage(static fn() => v::not(v::nfeAccessKey())->assert('52060433009911002506550120000007800267301615'));
-exceptionFullMessage(static fn() => v::nfeAccessKey()->assert('31841136830118868211870485416765268625116906'));
-exceptionFullMessage(static fn() => v::not(v::nfeAccessKey())->assert('52060433009911002506550120000007800267301615'));
-?>
---EXPECT--
-"31841136830118868211870485416765268625116906" must be a valid NFe access key
-"52060433009911002506550120000007800267301615" must not be a valid NFe access key
-- "31841136830118868211870485416765268625116906" must be a valid NFe access key
-- "52060433009911002506550120000007800267301615" must not be a valid NFe access key
diff --git a/tests/integration/rules/nif.phpt b/tests/integration/rules/nif.phpt
deleted file mode 100644
index c37580f7..00000000
--- a/tests/integration/rules/nif.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::nif()->assert('06357771Q'));
-exceptionMessage(static fn() => v::not(v::nif())->assert('71110316C'));
-exceptionFullMessage(static fn() => v::nif()->assert('06357771Q'));
-exceptionFullMessage(static fn() => v::not(v::nif())->assert('R1332622H'));
-?>
---EXPECT--
-"06357771Q" must be a valid NIF
-"71110316C" must not be a valid NIF
-- "06357771Q" must be a valid NIF
-- "R1332622H" must not be a valid NIF
\ No newline at end of file
diff --git a/tests/integration/rules/nip.phpt b/tests/integration/rules/nip.phpt
deleted file mode 100644
index 6d04f2e4..00000000
--- a/tests/integration/rules/nip.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::nip()->assert('1645865778'));
-exceptionMessage(static fn() => v::not(v::nip())->assert('1645865777'));
-exceptionFullMessage(static fn() => v::nip()->assert('1645865778'));
-exceptionFullMessage(static fn() => v::not(v::nip())->assert('1645865777'));
-?>
---EXPECT--
-"1645865778" must be a valid Polish VAT identification number
-"1645865777" must not be a valid Polish VAT identification number
-- "1645865778" must be a valid Polish VAT identification number
-- "1645865777" must not be a valid Polish VAT identification number
diff --git a/tests/integration/rules/no.phpt b/tests/integration/rules/no.phpt
deleted file mode 100644
index a92f6793..00000000
--- a/tests/integration/rules/no.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::not(v::no())->assert('No'));
-exceptionMessage(static fn() => v::no()->assert('Yes'));
-exceptionFullMessage(static fn() => v::not(v::no())->assert('No'));
-exceptionFullMessage(static fn() => v::no()->assert('Yes'));
-?>
---EXPECT--
-"No" must not be similar to "No"
-"Yes" must be similar to "No"
-- "No" must not be similar to "No"
-- "Yes" must be similar to "No"
diff --git a/tests/integration/rules/noWhitespace.phpt b/tests/integration/rules/noWhitespace.phpt
deleted file mode 100644
index 9befb89b..00000000
--- a/tests/integration/rules/noWhitespace.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::noWhitespace()->assert('w poiur'));
-exceptionMessage(static fn() => v::not(v::noWhitespace())->assert('wpoiur'));
-exceptionFullMessage(static fn() => v::noWhitespace()->assert('w poiur'));
-exceptionFullMessage(static fn() => v::not(v::noWhitespace())->assert('wpoiur'));
-?>
---EXPECT--
-"w poiur" must not contain whitespaces
-"wpoiur" must contain at least one whitespace
-- "w poiur" must not contain whitespaces
-- "wpoiur" must contain at least one whitespace
\ No newline at end of file
diff --git a/tests/integration/rules/noneOf.phpt b/tests/integration/rules/noneOf.phpt
deleted file mode 100644
index 065fcc7a..00000000
--- a/tests/integration/rules/noneOf.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- v::noneOf(v::intType(), v::positive())->assert(42));
-exceptionMessage(static fn() => v::not(v::noneOf(v::intType(), v::positive()))->assert('-1'));
-exceptionFullMessage(static fn() => v::noneOf(v::intType(), v::positive())->assert(42));
-exceptionFullMessage(static fn() => v::not(v::noneOf(v::intType(), v::positive()))->assert('-1'));
-?>
---EXPECT--
-42 must not be an integer
-"-1" must be an integer
-- None of these rules must pass for 42
- - 42 must not be an integer
- - 42 must not be a positive number
-- All of these rules must pass for "-1"
- - "-1" must be an integer
- - "-1" must be a positive number
\ No newline at end of file
diff --git a/tests/integration/rules/notBlank.phpt b/tests/integration/rules/notBlank.phpt
deleted file mode 100644
index 9bead4ad..00000000
--- a/tests/integration/rules/notBlank.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- v::notBlank()->assert(null));
-exceptionMessage(static fn() => v::notBlank()->setName('Field')->assert(null));
-exceptionMessage(static fn() => v::not(v::notBlank())->assert(1));
-exceptionFullMessage(static fn() => v::notBlank()->assert(''));
-exceptionFullMessage(static fn() => v::notBlank()->setName('Field')->assert(''));
-exceptionFullMessage(static fn() => v::not(v::notBlank())->assert([1]));
-?>
---EXPECT--
-The value must not be blank
-Field must not be blank
-1 must be blank
-- The value must not be blank
-- Field must not be blank
-- `[1]` must be blank
diff --git a/tests/integration/rules/notEmoji.phpt b/tests/integration/rules/notEmoji.phpt
deleted file mode 100644
index 90068105..00000000
--- a/tests/integration/rules/notEmoji.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::notEmoji()->assert('🍕'));
-exceptionMessage(static fn() => v::not(v::notEmoji())->assert('AB'));
-exceptionFullMessage(static fn() => v::notEmoji()->assert('🏄'));
-exceptionFullMessage(static fn() => v::not(v::notEmoji())->assert('YZ'));
-?>
---EXPECT--
-"🍕" must not contain an emoji
-"AB" must contain an emoji
-- "🏄" must not contain an emoji
-- "YZ" must contain an emoji
\ No newline at end of file
diff --git a/tests/integration/rules/notEmpty.phpt b/tests/integration/rules/notEmpty.phpt
deleted file mode 100644
index 857f7e89..00000000
--- a/tests/integration/rules/notEmpty.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- v::notEmpty()->assert(null));
-exceptionMessage(static fn() => v::notEmpty()->setName('Field')->assert(null));
-exceptionMessage(static fn() => v::not(v::notEmpty())->assert(1));
-exceptionFullMessage(static fn() => v::notEmpty()->assert(''));
-exceptionFullMessage(static fn() => v::notEmpty()->setName('Field')->assert(''));
-exceptionFullMessage(static fn() => v::not(v::notEmpty())->assert([1]));
-?>
---EXPECT--
-The value must not be empty
-Field must not be empty
-1 must be empty
-- The value must not be empty
-- Field must not be empty
-- `[1]` must be empty
diff --git a/tests/integration/rules/notUndef.phpt b/tests/integration/rules/notUndef.phpt
deleted file mode 100644
index a8666075..00000000
--- a/tests/integration/rules/notUndef.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::notUndef()->assert(null));
-exceptionMessage(static fn() => v::not(v::notUndef())->assert(0));
-exceptionMessage(static fn() => v::notUndef()->setName('Field')->assert(null));
-exceptionMessage(static fn() => v::not(v::notUndef()->setName('Field'))->assert([]));
-exceptionFullMessage(static fn() => v::notUndef()->assert(''));
-exceptionFullMessage(static fn() => v::not(v::notUndef())->assert([]));
-exceptionFullMessage(static fn() => v::notUndef()->setName('Field')->assert(''));
-exceptionFullMessage(static fn() => v::not(v::notUndef()->setName('Field'))->assert([]));
-?>
---EXPECT--
-The value must be defined
-The value must be undefined
-Field must be defined
-Field must be undefined
-- The value must be defined
-- The value must be undefined
-- Field must be defined
-- Field must be undefined
\ No newline at end of file
diff --git a/tests/integration/rules/nullOr.phpt b/tests/integration/rules/nullOr.phpt
deleted file mode 100644
index b2ad3b82..00000000
--- a/tests/integration/rules/nullOr.phpt
+++ /dev/null
@@ -1,138 +0,0 @@
---FILE--
- [v::nullOr(v::alpha()), 1234],
- 'Inverted wrapper' => [v::not(v::nullOr(v::alpha())), 'alpha'],
- 'Inverted wrapped' => [v::nullOr(v::not(v::alpha())), 'alpha'],
- 'Inverted nullined' => [v::not(v::nullOr(v::alpha())), null],
- 'Inverted nullined, wrapped name' => [v::not(v::nullOr(v::alpha()->setName('Wrapped'))), null],
- 'Inverted nullined, wrapper name' => [v::not(v::nullOr(v::alpha())->setName('Wrapper')), null],
- 'Inverted nullined, not name' => [v::not(v::nullOr(v::alpha()))->setName('Not'), null],
- 'With template' => [v::nullOr(v::alpha()), 123, 'Nine nimble numismatists near Naples'],
- 'With array template' => [v::nullOr(v::alpha()), 123, ['nullOrAlpha' => 'Next to nifty null notations']],
- 'Inverted nullined with template' => [
- v::not(v::nullOr(v::alpha())),
- null,
- ['notNullOrAlpha' => 'Next to nifty null notations'],
- ],
- 'Without subsequent result' => [
- v::nullOr(v::alpha()->stringType()),
- 1234,
- ],
- 'Without subsequent result with templates' => [
- v::nullOr(v::alpha()->stringType()),
- 1234,
- [
- 'nullOrAlpha' => 'Should be nul or alpha',
- 'nullOrStringType' => 'Should be nul or string type',
- ],
- ],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-1234 must contain only letters (a-z) or must be null
-- 1234 must contain only letters (a-z) or must be null
-[
- 'nullOrAlpha' => '1234 must contain only letters (a-z) or must be null',
-]
-
-Inverted wrapper
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"alpha" must not contain letters (a-z) and must not be null
-- "alpha" must not contain letters (a-z) and must not be null
-[
- 'notNullOrAlpha' => '"alpha" must not contain letters (a-z) and must not be null',
-]
-
-Inverted wrapped
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"alpha" must not contain letters (a-z) or must be null
-- "alpha" must not contain letters (a-z) or must be null
-[
- 'nullOrNotAlpha' => '"alpha" must not contain letters (a-z) or must be null',
-]
-
-Inverted nullined
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-`null` must not contain letters (a-z) and must not be null
-- `null` must not contain letters (a-z) and must not be null
-[
- 'notNullOrAlpha' => '`null` must not contain letters (a-z) and must not be null',
-]
-
-Inverted nullined, wrapped name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not contain letters (a-z) and must not be null
-- Wrapped must not contain letters (a-z) and must not be null
-[
- 'notNullOrAlpha' => 'Wrapped must not contain letters (a-z) and must not be null',
-]
-
-Inverted nullined, wrapper name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must not contain letters (a-z) and must not be null
-- Wrapper must not contain letters (a-z) and must not be null
-[
- 'notNullOrAlpha' => 'Wrapper must not contain letters (a-z) and must not be null',
-]
-
-Inverted nullined, not name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not must not contain letters (a-z) and must not be null
-- Not must not contain letters (a-z) and must not be null
-[
- 'notNullOrAlpha' => 'Not must not contain letters (a-z) and must not be null',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Nine nimble numismatists near Naples
-- Nine nimble numismatists near Naples
-[
- 'nullOrAlpha' => 'Nine nimble numismatists near Naples',
-]
-
-With array template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Next to nifty null notations
-- Next to nifty null notations
-[
- 'nullOrAlpha' => 'Next to nifty null notations',
-]
-
-Inverted nullined with template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Next to nifty null notations
-- Next to nifty null notations
-[
- 'notNullOrAlpha' => 'Next to nifty null notations',
-]
-
-Without subsequent result
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-1234 must contain only letters (a-z) or must be null
-- All of the required rules must pass for 1234
- - 1234 must contain only letters (a-z) or must be null
- - 1234 must be a string or must be null
-[
- '__root__' => 'All of the required rules must pass for 1234',
- 'nullOrAlpha' => '1234 must contain only letters (a-z) or must be null',
- 'nullOrStringType' => '1234 must be a string or must be null',
-]
-
-Without subsequent result with templates
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Should be nul or alpha
-- All of the required rules must pass for 1234
- - Should be nul or alpha
- - Should be nul or string type
-[
- '__root__' => 'All of the required rules must pass for 1234',
- 'nullOrAlpha' => 'Should be nul or alpha',
- 'nullOrStringType' => 'Should be nul or string type',
-]
diff --git a/tests/integration/rules/nullType.phpt b/tests/integration/rules/nullType.phpt
deleted file mode 100644
index e549ab0e..00000000
--- a/tests/integration/rules/nullType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::nullType()->assert(''));
-exceptionMessage(static fn() => v::not(v::nullType())->assert(null));
-exceptionFullMessage(static fn() => v::nullType()->assert(false));
-exceptionFullMessage(static fn() => v::not(v::nullType())->assert(null));
-?>
---EXPECT--
-"" must be null
-`null` must not be null
-- `false` must be null
-- `null` must not be null
diff --git a/tests/integration/rules/number.phpt b/tests/integration/rules/number.phpt
deleted file mode 100644
index 25b5b98c..00000000
--- a/tests/integration/rules/number.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::number()->assert(acos(1.01)));
-exceptionMessage(static fn() => v::not(v::number())->assert(42));
-exceptionFullMessage(static fn() => v::number()->assert(NAN));
-exceptionFullMessage(static fn() => v::not(v::number())->assert(42));
-?>
---EXPECT--
-`NaN` must be a valid number
-42 must not be a number
-- `NaN` must be a valid number
-- 42 must not be a number
\ No newline at end of file
diff --git a/tests/integration/rules/numericVal.phpt b/tests/integration/rules/numericVal.phpt
deleted file mode 100644
index 4c6f357c..00000000
--- a/tests/integration/rules/numericVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::numericVal()->assert('a'));
-exceptionMessage(static fn() => v::not(v::numericVal())->assert('1'));
-exceptionFullMessage(static fn() => v::numericVal()->assert('a'));
-exceptionFullMessage(static fn() => v::not(v::numericVal())->assert('1'));
-?>
---EXPECT--
-"a" must be a numeric value
-"1" must not be a numeric value
-- "a" must be a numeric value
-- "1" must not be a numeric value
\ No newline at end of file
diff --git a/tests/integration/rules/objectType.phpt b/tests/integration/rules/objectType.phpt
deleted file mode 100644
index 42177e96..00000000
--- a/tests/integration/rules/objectType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::objectType()->assert([]));
-exceptionMessage(static fn() => v::not(v::objectType())->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::objectType()->assert('test'));
-exceptionFullMessage(static fn() => v::not(v::objectType())->assert(new ArrayObject()));
-?>
---EXPECT--
-`[]` must be an object
-`stdClass {}` must not be an object
-- "test" must be an object
-- `ArrayObject { getArrayCopy() => [] }` must not be an object
\ No newline at end of file
diff --git a/tests/integration/rules/odd.phpt b/tests/integration/rules/odd.phpt
deleted file mode 100644
index d0604afb..00000000
--- a/tests/integration/rules/odd.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::odd()->assert(2));
-exceptionMessage(static fn() => v::not(v::odd())->assert(7));
-exceptionFullMessage(static fn() => v::odd()->assert(2));
-exceptionFullMessage(static fn() => v::not(v::odd())->assert(9));
-?>
---EXPECT--
-2 must be an odd number
-7 must be an even number
-- 2 must be an odd number
-- 9 must be an even number
\ No newline at end of file
diff --git a/tests/integration/rules/perfectSquare.phpt b/tests/integration/rules/perfectSquare.phpt
deleted file mode 100644
index eea3f98e..00000000
--- a/tests/integration/rules/perfectSquare.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::perfectSquare()->assert(250));
-exceptionMessage(static fn() => v::not(v::perfectSquare())->assert(9));
-exceptionFullMessage(static fn() => v::perfectSquare()->assert(7));
-exceptionFullMessage(static fn() => v::not(v::perfectSquare())->assert(400));
-?>
---EXPECT--
-250 must be a perfect square number
-9 must not be a perfect square number
-- 7 must be a perfect square number
-- 400 must not be a perfect square number
\ No newline at end of file
diff --git a/tests/integration/rules/pesel.phpt b/tests/integration/rules/pesel.phpt
deleted file mode 100644
index 8834c7f5..00000000
--- a/tests/integration/rules/pesel.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::pesel()->assert('21120209251'));
-exceptionMessage(static fn() => v::not(v::pesel())->assert('21120209256'));
-exceptionFullMessage(static fn() => v::pesel()->assert('21120209251'));
-exceptionFullMessage(static fn() => v::not(v::pesel())->assert('21120209256'));
-?>
---EXPECT--;
-"21120209251" must be a valid PESEL
-"21120209256" must not be a valid PESEL
-- "21120209251" must be a valid PESEL
-- "21120209256" must not be a valid PESEL
diff --git a/tests/integration/rules/phone.phpt b/tests/integration/rules/phone.phpt
deleted file mode 100644
index 07315003..00000000
--- a/tests/integration/rules/phone.phpt
+++ /dev/null
@@ -1,53 +0,0 @@
---FILE--
- [v::phone(), '123'],
- 'Country-specific' => [v::phone('BR'), '+1 650 253 00 00'],
- 'Inverted' => [v::not(v::phone()), '+55 11 91111 1111'],
- 'Default with name' => [v::phone()->setName('Phone'), '123'],
- 'Country-specific with name' => [v::phone('US')->setName('Phone'), '123'],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-"123" must be a valid telephone number
-- "123" must be a valid telephone number
-[
- 'phone' => '"123" must be a valid telephone number',
-]
-
-Country-specific
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"+1 650 253 00 00" must be a valid telephone number for country Brazil
-- "+1 650 253 00 00" must be a valid telephone number for country Brazil
-[
- 'phone' => '"+1 650 253 00 00" must be a valid telephone number for country Brazil',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-"+55 11 91111 1111" must not be a valid telephone number
-- "+55 11 91111 1111" must not be a valid telephone number
-[
- 'notPhone' => '"+55 11 91111 1111" must not be a valid telephone number',
-]
-
-Default with name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Phone must be a valid telephone number
-- Phone must be a valid telephone number
-[
- 'phone' => 'Phone must be a valid telephone number',
-]
-
-Country-specific with name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Phone must be a valid telephone number for country United States
-- Phone must be a valid telephone number for country United States
-[
- 'phone' => 'Phone must be a valid telephone number for country United States',
-]
diff --git a/tests/integration/rules/phplabel.phpt b/tests/integration/rules/phplabel.phpt
deleted file mode 100644
index a757fc57..00000000
--- a/tests/integration/rules/phplabel.phpt
+++ /dev/null
@@ -1,17 +0,0 @@
---TEST--
-PhpLabel rule exception should not be thrown for valid inputs
---FILE--
- v::phpLabel()->assert('f o o'));
-exceptionMessage(static fn() => v::not(v::phpLabel())->assert('correctOne'));
-exceptionFullMessage(static fn() => v::phpLabel()->assert('0wner'));
-exceptionFullMessage(static fn() => v::not(v::phpLabel())->assert('Respect'));
-?>
---EXPECT--
-"f o o" must be a valid PHP label
-"correctOne" must not be a valid PHP label
-- "0wner" must be a valid PHP label
-- "Respect" must not be a valid PHP label
diff --git a/tests/integration/rules/pis.phpt b/tests/integration/rules/pis.phpt
deleted file mode 100644
index 6fe62e13..00000000
--- a/tests/integration/rules/pis.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::pis()->assert('this thing'));
-exceptionMessage(static fn() => v::not(v::pis())->assert('120.6671.406-4'));
-exceptionFullMessage(static fn() => v::pis()->assert('your mother'));
-exceptionFullMessage(static fn() => v::not(v::pis())->assert('120.9378.174-5'));
-?>
---EXPECT--
-"this thing" must be a valid PIS number
-"120.6671.406-4" must not be a valid PIS number
-- "your mother" must be a valid PIS number
-- "120.9378.174-5" must not be a valid PIS number
diff --git a/tests/integration/rules/polishIdCard.phpt b/tests/integration/rules/polishIdCard.phpt
deleted file mode 100644
index 19f39d67..00000000
--- a/tests/integration/rules/polishIdCard.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::polishIdCard()->assert('AYE205411'));
-exceptionMessage(static fn() => v::not(v::polishIdCard())->assert('AYE205410'));
-exceptionFullMessage(static fn() => v::polishIdCard()->assert('AYE205411'));
-exceptionFullMessage(static fn() => v::not(v::polishIdCard())->assert('AYE205410'));
-?>
---EXPECT--
-"AYE205411" must be a valid Polish Identity Card number
-"AYE205410" must not be a valid Polish Identity Card number
-- "AYE205411" must be a valid Polish Identity Card number
-- "AYE205410" must not be a valid Polish Identity Card number
diff --git a/tests/integration/rules/positive.phpt b/tests/integration/rules/positive.phpt
deleted file mode 100644
index 7c9f6633..00000000
--- a/tests/integration/rules/positive.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::positive()->assert(-10));
-exceptionMessage(static fn() => v::not(v::positive())->assert(16));
-exceptionFullMessage(static fn() => v::positive()->assert('a'));
-exceptionFullMessage(static fn() => v::not(v::positive())->assert('165'));
-?>
---EXPECT--
--10 must be a positive number
-16 must not be a positive number
-- "a" must be a positive number
-- "165" must not be a positive number
\ No newline at end of file
diff --git a/tests/integration/rules/postalCode.phpt b/tests/integration/rules/postalCode.phpt
deleted file mode 100644
index dbbc0de5..00000000
--- a/tests/integration/rules/postalCode.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::postalCode('BR')->assert('1057BV'));
-exceptionMessage(static fn() => v::not(v::postalCode('NL'))->assert('1057BV'));
-exceptionFullMessage(static fn() => v::postalCode('BR')->assert('1057BV'));
-exceptionFullMessage(static fn() => v::not(v::postalCode('NL'))->assert('1057BV'));
-?>
---EXPECT--
-"1057BV" must be a valid postal code on "BR"
-"1057BV" must not be a valid postal code on "NL"
-- "1057BV" must be a valid postal code on "BR"
-- "1057BV" must not be a valid postal code on "NL"
diff --git a/tests/integration/rules/primeNumber.phpt b/tests/integration/rules/primeNumber.phpt
deleted file mode 100644
index 81ae03ea..00000000
--- a/tests/integration/rules/primeNumber.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::primeNumber()->assert(10));
-exceptionMessage(static fn() => v::not(v::primeNumber())->assert(3));
-exceptionFullMessage(static fn() => v::primeNumber()->assert('Foo'));
-exceptionFullMessage(static fn() => v::not(v::primeNumber())->assert('+7'));
-?>
---EXPECT--
-10 must be a prime number
-3 must not be a prime number
-- "Foo" must be a prime number
-- "+7" must not be a prime number
\ No newline at end of file
diff --git a/tests/integration/rules/printable.phpt b/tests/integration/rules/printable.phpt
deleted file mode 100644
index 486b4df2..00000000
--- a/tests/integration/rules/printable.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::printable()->assert(''));
-exceptionMessage(static fn() => v::not(v::printable())->assert('abc'));
-exceptionFullMessage(static fn() => v::printable()->assert('foo' . chr(10) . 'bar'));
-exceptionFullMessage(static fn() => v::not(v::printable())->assert('$%asd'));
-?>
---EXPECT--
-"" must contain only printable characters
-"abc" must not contain printable characters
-- "foo\nbar" must contain only printable characters
-- "$%asd" must not contain printable characters
diff --git a/tests/integration/rules/property.phpt b/tests/integration/rules/property.phpt
deleted file mode 100644
index 8daa950a..00000000
--- a/tests/integration/rules/property.phpt
+++ /dev/null
@@ -1,162 +0,0 @@
---FILE--
- [v::property('foo', v::intType()), new stdClass()],
- 'Default' => [v::property('foo', v::intType()), (object) ['foo' => 'string']],
- 'Inverted' => [v::not(v::property('foo', v::intType())), (object) ['foo' => 12]],
- 'Double-inverted with missing property' => [
- v::not(v::not(v::property('foo', v::intType()))),
- new stdClass(),
- ],
-
- // With custom name
- 'With wrapped name, missing property' => [
- v::property('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'),
- new stdClass(),
- ],
- 'With wrapped name, default' => [
- v::property('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'),
- (object) ['foo' => 'string'],
- ],
- 'With wrapped name, inverted' => [
- v::not(v::property('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not'),
- (object) ['foo' => 12],
- ],
- 'With wrapper name, default' => [
- v::property('foo', v::intType())->setName('Wrapper'),
- (object) ['foo' => 'string'],
- ],
- 'With wrapper name, missing property' => [
- v::property('foo', v::intType())->setName('Wrapper'),
- new stdClass(),
- ],
- 'With wrapper name, inverted' => [
- v::not(v::property('foo', v::intType())->setName('Wrapper'))->setName('Not'),
- (object) ['foo' => 12],
- ],
- 'With "Not" name, inverted' => [
- v::not(v::property('foo', v::intType()))->setName('Not'),
- (object) ['foo' => 12],
- ],
-
- // With custom template
- 'With template, default' => [
- v::property('foo', v::intType()),
- (object) ['foo' => 'string'],
- 'Particularly precautions perplexing property',
- ],
- 'With template, inverted' => [
- v::not(v::property('foo', v::intType())),
- (object) ['foo' => 12],
- 'Not a prompt prospect of a particularly primitive property',
- ],
-]);
-?>
---EXPECT--
-Missing property
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-Default
-⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-Double-inverted with missing property
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-With wrapped name, missing property
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be present
-- Wrapped must be present
-[
- 'foo' => 'Wrapped must be present',
-]
-
-With wrapped name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be an integer
-- Wrapped must be an integer
-[
- 'foo' => 'Wrapped must be an integer',
-]
-
-With wrapped name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be an integer
-- Wrapped must not be an integer
-[
- 'foo' => 'Wrapped must not be an integer',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-With wrapper name, missing property
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With "Not" name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Particularly precautions perplexing property
-- Particularly precautions perplexing property
-[
- 'foo' => 'Particularly precautions perplexing property',
-]
-
-With template, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not a prompt prospect of a particularly primitive property
-- Not a prompt prospect of a particularly primitive property
-[
- 'foo' => 'Not a prompt prospect of a particularly primitive property',
-]
diff --git a/tests/integration/rules/propertyExists.phpt b/tests/integration/rules/propertyExists.phpt
deleted file mode 100644
index 72d8eb8d..00000000
--- a/tests/integration/rules/propertyExists.phpt
+++ /dev/null
@@ -1,44 +0,0 @@
---FILE--
- [v::propertyExists('foo'), (object) ['bar' => 'baz']],
- 'Inverted mode' => [v::not(v::propertyExists('foo')), (object) ['foo' => 'baz']],
- 'Custom name' => [v::propertyExists('foo')->setName('Custom name'), (object) ['bar' => 'baz']],
- 'Custom template' => [v::propertyExists('foo'), (object) ['bar' => 'baz'], 'Custom template for `{{name}}`'],
-]);
-?>
---EXPECT--
-Default mode
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-Inverted mode
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be present
-- foo must not be present
-[
- 'foo' => 'foo must not be present',
-]
-
-Custom name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Custom name must be present
-- Custom name must be present
-[
- 'foo' => 'Custom name must be present',
-]
-
-Custom template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Custom template for `foo`
-- Custom template for `foo`
-[
- 'foo' => 'Custom template for `foo`',
-]
diff --git a/tests/integration/rules/propertyOptional.phpt b/tests/integration/rules/propertyOptional.phpt
deleted file mode 100644
index 0f69a6f4..00000000
--- a/tests/integration/rules/propertyOptional.phpt
+++ /dev/null
@@ -1,129 +0,0 @@
---FILE--
- [v::propertyOptional('foo', v::intType()), (object) ['foo' => 'string']],
- 'Inverted' => [v::not(v::propertyOptional('foo', v::intType())), (object) ['foo' => 12]],
- 'Inverted with missing property' => [
- v::not(v::propertyOptional('foo', v::intType())),
- new stdClass(),
- ],
-
- // With custom name
- 'With wrapped name, default' => [
- v::propertyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'),
- (object) ['foo' => 'string'],
- ],
- 'With wrapped name, inverted' => [
- v::not(v::propertyOptional('foo', v::intType()->setName('Wrapped'))->setName('Wrapper'))->setName('Not'),
- (object) ['foo' => 12],
- ],
- 'With wrapper name, default' => [
- v::propertyOptional('foo', v::intType())->setName('Wrapper'),
- (object) ['foo' => 'string'],
- ],
- 'With wrapper name, inverted' => [
- v::not(v::propertyOptional('foo', v::intType())->setName('Wrapper'))->setName('Not'),
- (object) ['foo' => 12],
- ],
- 'With "Not" name, inverted' => [
- v::not(v::propertyOptional('foo', v::intType()))->setName('Not'),
- (object) ['foo' => 12],
- ],
-
- // With custom template
- 'With template, default' => [
- v::propertyOptional('foo', v::intType()),
- (object) ['foo' => 'string'],
- 'Proper property planners plan precise property plots',
- ],
- 'With template, inverted' => [
- v::not(v::propertyOptional('foo', v::intType())),
- (object) ['foo' => 12],
- 'Not proving prudent property planning promotes prosperity',
- ],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-Inverted
-⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-Inverted with missing property
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be present
-- foo must be present
-[
- 'foo' => 'foo must be present',
-]
-
-With wrapped name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must be an integer
-- Wrapped must be an integer
-[
- 'foo' => 'Wrapped must be an integer',
-]
-
-With wrapped name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not be an integer
-- Wrapped must not be an integer
-[
- 'foo' => 'Wrapped must not be an integer',
-]
-
-With wrapper name, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be an integer
-- foo must be an integer
-[
- 'foo' => 'foo must be an integer',
-]
-
-With wrapper name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With "Not" name, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-foo must not be an integer
-- foo must not be an integer
-[
- 'foo' => 'foo must not be an integer',
-]
-
-With template, default
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Proper property planners plan precise property plots
-- Proper property planners plan precise property plots
-[
- 'foo' => 'Proper property planners plan precise property plots',
-]
-
-With template, inverted
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not proving prudent property planning promotes prosperity
-- Not proving prudent property planning promotes prosperity
-[
- 'foo' => 'Not proving prudent property planning promotes prosperity',
-]
diff --git a/tests/integration/rules/punct.phpt b/tests/integration/rules/punct.phpt
deleted file mode 100644
index b04fd08a..00000000
--- a/tests/integration/rules/punct.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::punct()->assert('a'));
-exceptionMessage(static fn() => v::punct('c')->assert('b'));
-exceptionMessage(static fn() => v::not(v::punct())->assert('.'));
-exceptionMessage(static fn() => v::not(v::punct('d'))->assert('?'));
-exceptionFullMessage(static fn() => v::punct()->assert('e'));
-exceptionFullMessage(static fn() => v::punct('f')->assert('g'));
-exceptionFullMessage(static fn() => v::not(v::punct())->assert('!'));
-exceptionFullMessage(static fn() => v::not(v::punct('h'))->assert(';'));
-?>
---EXPECT--
-"a" must contain only punctuation characters
-"b" must contain only punctuation characters and "c"
-"." must not contain punctuation characters
-"?" must not contain punctuation characters or "d"
-- "e" must contain only punctuation characters
-- "g" must contain only punctuation characters and "f"
-- "!" must not contain punctuation characters
-- ";" must not contain punctuation characters or "h"
diff --git a/tests/integration/rules/readable.phpt b/tests/integration/rules/readable.phpt
deleted file mode 100644
index a1dcb1cc..00000000
--- a/tests/integration/rules/readable.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::readable()->assert('tests/fixtures/invalid-image.jpg'));
-exceptionMessage(static fn() => v::not(v::readable())->assert('tests/fixtures/valid-image.png'));
-exceptionFullMessage(static fn() => v::readable()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::readable())->assert('tests/fixtures/valid-image.png'));
-?>
---EXPECT--
-"tests/fixtures/invalid-image.jpg" must be readable
-"tests/fixtures/valid-image.png" must not be readable
-- `stdClass {}` must be readable
-- "tests/fixtures/valid-image.png" must not be readable
diff --git a/tests/integration/rules/regex.phpt b/tests/integration/rules/regex.phpt
deleted file mode 100644
index c6aebdba..00000000
--- a/tests/integration/rules/regex.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::regex('/^w+$/')->assert('w poiur'));
-exceptionMessage(static fn() => v::not(v::regex('/^[a-z]+$/'))->assert('wpoiur'));
-exceptionFullMessage(static fn() => v::regex('/^w+$/')->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::regex('/^[a-z]+$/i'))->assert('wPoiur'));
-?>
---EXPECT--
-"w poiur" must match the pattern `/^w+$/`
-"wpoiur" must not match the pattern `/^[a-z]+$/`
-- `stdClass {}` must match the pattern `/^w+$/`
-- "wPoiur" must not match the pattern `/^[a-z]+$/i`
\ No newline at end of file
diff --git a/tests/integration/rules/resourceType.phpt b/tests/integration/rules/resourceType.phpt
deleted file mode 100644
index 6b40baa3..00000000
--- a/tests/integration/rules/resourceType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::resourceType()->assert('test'));
-exceptionMessage(static fn() => v::not(v::resourceType())->assert(tmpfile()));
-exceptionFullMessage(static fn() => v::resourceType()->assert([]));
-exceptionFullMessage(static fn() => v::not(v::resourceType())->assert(tmpfile()));
-?>
---EXPECT--
-"test" must be a resource
-`resource ` must not be a resource
-- `[]` must be a resource
-- `resource ` must not be a resource
diff --git a/tests/integration/rules/roman.phpt b/tests/integration/rules/roman.phpt
deleted file mode 100644
index 5702ba4e..00000000
--- a/tests/integration/rules/roman.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::roman()->assert(1234));
-exceptionMessage(static fn() => v::not(v::roman())->assert('XL'));
-exceptionFullMessage(static fn() => v::roman()->assert('e2'));
-exceptionFullMessage(static fn() => v::not(v::roman())->assert('IV'));
-?>
---EXPECT--
-1234 must be a valid Roman numeral
-"XL" must not be a valid Roman numeral
-- "e2" must be a valid Roman numeral
-- "IV" must not be a valid Roman numeral
diff --git a/tests/integration/rules/scalarVal.phpt b/tests/integration/rules/scalarVal.phpt
deleted file mode 100644
index f170e8e2..00000000
--- a/tests/integration/rules/scalarVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::scalarVal()->assert([]));
-exceptionMessage(static fn() => v::not(v::scalarVal())->assert(true));
-exceptionFullMessage(static fn() => v::scalarVal()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::scalarVal())->assert(42));
-?>
---EXPECT--
-`[]` must be a scalar value
-`true` must not be a scalar value
-- `stdClass {}` must be a scalar value
-- 42 must not be a scalar value
diff --git a/tests/integration/rules/size.phpt b/tests/integration/rules/size.phpt
deleted file mode 100644
index 1163ce12..00000000
--- a/tests/integration/rules/size.phpt
+++ /dev/null
@@ -1,31 +0,0 @@
---FILE--
- v::size('1kb', '2kb')->assert('tests/fixtures/valid-image.gif'));
-exceptionMessage(static fn() => v::size('700kb', null)->assert('tests/fixtures/valid-image.gif'));
-exceptionMessage(static fn() => v::size(null, '1kb')->assert('tests/fixtures/valid-image.gif'));
-exceptionMessage(static fn() => v::not(v::size('500kb', '600kb'))->assert('tests/fixtures/valid-image.gif'));
-exceptionMessage(static fn() => v::not(v::size('500kb', null))->assert('tests/fixtures/valid-image.gif'));
-exceptionMessage(static fn() => v::not(v::size(null, '600kb'))->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::size('1kb', '2kb')->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::size('700kb', null)->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::size(null, '1kb')->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::not(v::size('500kb', '600kb'))->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::not(v::size('500kb', null))->assert('tests/fixtures/valid-image.gif'));
-exceptionFullMessage(static fn() => v::not(v::size(null, '600kb'))->assert('tests/fixtures/valid-image.gif'));
-?>
---EXPECT--
-"tests/fixtures/valid-image.gif" must be between "1kb" and "2kb"
-"tests/fixtures/valid-image.gif" must be greater than "700kb"
-"tests/fixtures/valid-image.gif" must be lower than "1kb"
-"tests/fixtures/valid-image.gif" must not be between "500kb" and "600kb"
-"tests/fixtures/valid-image.gif" must not be greater than "500kb"
-"tests/fixtures/valid-image.gif" must not be lower than "600kb"
-- "tests/fixtures/valid-image.gif" must be between "1kb" and "2kb"
-- "tests/fixtures/valid-image.gif" must be greater than "700kb"
-- "tests/fixtures/valid-image.gif" must be lower than "1kb"
-- "tests/fixtures/valid-image.gif" must not be between "500kb" and "600kb"
-- "tests/fixtures/valid-image.gif" must not be greater than "500kb"
-- "tests/fixtures/valid-image.gif" must not be lower than "600kb"
diff --git a/tests/integration/rules/slug.phpt b/tests/integration/rules/slug.phpt
deleted file mode 100644
index eb14898e..00000000
--- a/tests/integration/rules/slug.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::slug()->assert('my-Slug'));
-exceptionMessage(static fn() => v::not(v::slug())->assert('my-slug'));
-exceptionFullMessage(static fn() => v::slug()->assert('my-Slug'));
-exceptionFullMessage(static fn() => v::not(v::slug())->assert('my-slug'));
-?>
---EXPECT--
-"my-Slug" must be a valid slug
-"my-slug" must not be a valid slug
-- "my-Slug" must be a valid slug
-- "my-slug" must not be a valid slug
diff --git a/tests/integration/rules/sorted.phpt b/tests/integration/rules/sorted.phpt
deleted file mode 100644
index 46392be9..00000000
--- a/tests/integration/rules/sorted.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::sorted('ASC')->assert([1, 3, 2]));
-exceptionMessage(static fn() => v::sorted('DESC')->assert([1, 2, 3]));
-exceptionMessage(static fn() => v::not(v::sorted('ASC'))->assert([1, 2, 3]));
-exceptionMessage(static fn() => v::not(v::sorted('DESC'))->assert([3, 2, 1]));
-exceptionFullMessage(static fn() => v::sorted('ASC')->assert([3, 2, 1]));
-exceptionFullMessage(static fn() => v::sorted('DESC')->assert([1, 2, 3]));
-exceptionFullMessage(static fn() => v::not(v::sorted('ASC'))->assert([1, 2, 3]));
-exceptionFullMessage(static fn() => v::not(v::sorted('DESC'))->assert([3, 2, 1]));
-?>
---EXPECT--
-`[1, 3, 2]` must be sorted in ascending order
-`[1, 2, 3]` must be sorted in descending order
-`[1, 2, 3]` must not be sorted in ascending order
-`[3, 2, 1]` must not be sorted in descending order
-- `[3, 2, 1]` must be sorted in ascending order
-- `[1, 2, 3]` must be sorted in descending order
-- `[1, 2, 3]` must not be sorted in ascending order
-- `[3, 2, 1]` must not be sorted in descending order
diff --git a/tests/integration/rules/space.phpt b/tests/integration/rules/space.phpt
deleted file mode 100644
index f30a5dc7..00000000
--- a/tests/integration/rules/space.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::space()->assert('ab'));
-exceptionMessage(static fn() => v::space('c')->assert('cd'));
-exceptionMessage(static fn() => v::not(v::space())->assert("\t"));
-exceptionMessage(static fn() => v::not(v::space('def'))->assert("\r"));
-exceptionFullMessage(static fn() => v::space()->assert('ef'));
-exceptionFullMessage(static fn() => v::space('e')->assert('gh'));
-exceptionFullMessage(static fn() => v::not(v::space())->assert("\n"));
-exceptionFullMessage(static fn() => v::not(v::space('yk'))->assert(' k'));
-?>
---EXPECT--
-"ab" must contain only space characters
-"cd" must contain only space characters and "c"
-"\t" must not contain space characters
-"\r" must not contain space characters or "def"
-- "ef" must contain only space characters
-- "gh" must contain only space characters and "e"
-- "\n" must not contain space characters
-- " k" must not contain space characters or "yk"
diff --git a/tests/integration/rules/startsWith.phpt b/tests/integration/rules/startsWith.phpt
deleted file mode 100644
index 9783c3a8..00000000
--- a/tests/integration/rules/startsWith.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::startsWith('b')->assert(['a', 'b']));
-exceptionMessage(static fn() => v::not(v::startsWith(1.1))->assert([1.1, 2.2]));
-exceptionFullMessage(static fn() => v::startsWith('3.3', true)->assert([3.3, 4.4]));
-exceptionFullMessage(static fn() => v::not(v::startsWith('c'))->assert(['c', 'd']));
-?>
---EXPECT--
-`["a", "b"]` must start with "b"
-`[1.1, 2.2]` must not start with 1.1
-- `[3.3, 4.4]` must start with "3.3"
-- `["c", "d"]` must not start with "c"
diff --git a/tests/integration/rules/stringType.phpt b/tests/integration/rules/stringType.phpt
deleted file mode 100644
index 028e51f7..00000000
--- a/tests/integration/rules/stringType.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::stringType()->assert(42));
-exceptionMessage(static fn() => v::not(v::stringType())->assert('foo'));
-exceptionFullMessage(static fn() => v::stringType()->assert(true));
-exceptionFullMessage(static fn() => v::not(v::stringType())->assert('bar'));
-?>
---EXPECT--
-42 must be a string
-"foo" must not be a string
-- `true` must be a string
-- "bar" must not be a string
\ No newline at end of file
diff --git a/tests/integration/rules/stringVal.phpt b/tests/integration/rules/stringVal.phpt
deleted file mode 100644
index a1733e29..00000000
--- a/tests/integration/rules/stringVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::stringVal()->assert([]));
-exceptionMessage(static fn() => v::not(v::stringVal())->assert(true));
-exceptionFullMessage(static fn() => v::stringVal()->assert(new stdClass()));
-exceptionFullMessage(static fn() => v::not(v::stringVal())->assert(42));
-?>
---EXPECT--
-`[]` must be a string value
-`true` must not be a string value
-- `stdClass {}` must be a string value
-- 42 must not be a string value
\ No newline at end of file
diff --git a/tests/integration/rules/subset.phpt b/tests/integration/rules/subset.phpt
deleted file mode 100644
index c980b1d9..00000000
--- a/tests/integration/rules/subset.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::subset([1, 2])->assert([1, 2, 3]));
-exceptionMessage(static fn() => v::not(v::subset([1, 2, 3]))->assert([1, 2]));
-exceptionFullMessage(static fn() => v::subset(['A', 'B'])->assert(['B', 'C']));
-exceptionFullMessage(static fn() => v::not(v::subset(['A']))->assert(['A']));
-?>
---EXPECT--
-`[1, 2, 3]` must be subset of `[1, 2]`
-`[1, 2]` must not be subset of `[1, 2, 3]`
-- `["B", "C"]` must be subset of `["A", "B"]`
-- `["A"]` must not be subset of `["A"]`
diff --git a/tests/integration/rules/symbolicLink.phpt b/tests/integration/rules/symbolicLink.phpt
deleted file mode 100644
index 014d5d14..00000000
--- a/tests/integration/rules/symbolicLink.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::symbolicLink()->assert('tests/fixtures/fake-filename'));
-exceptionMessage(static fn() => v::not(v::symbolicLink())->assert('tests/fixtures/symbolic-link'));
-exceptionFullMessage(static fn() => v::symbolicLink()->assert('tests/fixtures/fake-filename'));
-exceptionFullMessage(static fn() => v::not(v::symbolicLink())->assert('tests/fixtures/symbolic-link'));
-?>
---EXPECT--
-"tests/fixtures/fake-filename" must be a symbolic link
-"tests/fixtures/symbolic-link" must not be a symbolic link
-- "tests/fixtures/fake-filename" must be a symbolic link
-- "tests/fixtures/symbolic-link" must not be a symbolic link
diff --git a/tests/integration/rules/time.phpt b/tests/integration/rules/time.phpt
deleted file mode 100644
index d4e1c2aa..00000000
--- a/tests/integration/rules/time.phpt
+++ /dev/null
@@ -1,17 +0,0 @@
---FILE--
- v::time()->assert('2018-01-30'));
-exceptionMessage(static fn() => v::not(v::time())->assert('09:25:46'));
-exceptionFullMessage(static fn() => v::time()->assert('2018-01-30'));
-exceptionFullMessage(static fn() => v::not(v::time('g:i A'))->assert('8:13 AM'));
-?>
---EXPECT--
-"2018-01-30" must be a valid time in the format "23:59:59"
-"09:25:46" must not be a valid time in the format "23:59:59"
-- "2018-01-30" must be a valid time in the format "23:59:59"
-- "8:13 AM" must not be a valid time in the format "11:59 PM"
diff --git a/tests/integration/rules/tld.phpt b/tests/integration/rules/tld.phpt
deleted file mode 100644
index 813af4b6..00000000
--- a/tests/integration/rules/tld.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::tld()->assert('42'));
-exceptionMessage(static fn() => v::not(v::tld())->assert('com'));
-exceptionFullMessage(static fn() => v::tld()->assert('1984'));
-exceptionFullMessage(static fn() => v::not(v::tld())->assert('com'));
-?>
---EXPECT--
-"42" must be a valid top-level domain name
-"com" must not be a valid top-level domain name
-- "1984" must be a valid top-level domain name
-- "com" must not be a valid top-level domain name
diff --git a/tests/integration/rules/trueVal.phpt b/tests/integration/rules/trueVal.phpt
deleted file mode 100644
index 6d6d487b..00000000
--- a/tests/integration/rules/trueVal.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::trueVal()->assert(false));
-exceptionMessage(static fn() => v::not(v::trueVal())->assert(1));
-exceptionFullMessage(static fn() => v::trueVal()->assert(0));
-exceptionFullMessage(static fn() => v::not(v::trueVal())->assert('true'));
-?>
---EXPECT--
-`false` must evaluate to `true`
-1 must not evaluate to `true`
-- 0 must evaluate to `true`
-- "true" must not evaluate to `true`
diff --git a/tests/integration/rules/undefOr.phpt b/tests/integration/rules/undefOr.phpt
deleted file mode 100644
index c6d9581b..00000000
--- a/tests/integration/rules/undefOr.phpt
+++ /dev/null
@@ -1,138 +0,0 @@
---FILE--
- [v::undefOr(v::alpha()), 1234],
- 'Inverted wrapper' => [v::not(v::undefOr(v::alpha())), 'alpha'],
- 'Inverted wrapped' => [v::undefOr(v::not(v::alpha())), 'alpha'],
- 'Inverted undefined' => [v::not(v::undefOr(v::alpha())), null],
- 'Inverted undefined, wrapped name' => [v::not(v::undefOr(v::alpha()->setName('Wrapped'))), null],
- 'Inverted undefined, wrapper name' => [v::not(v::undefOr(v::alpha())->setName('Wrapper')), null],
- 'Inverted undefined, not name' => [v::not(v::undefOr(v::alpha()))->setName('Not'), null],
- 'With template' => [v::undefOr(v::alpha()), 123, 'Underneath the undulating umbrella'],
- 'With array template' => [v::undefOr(v::alpha()), 123, ['undefOrAlpha' => 'Undefined number of unique unicorns']],
- 'Inverted undefined with template' => [
- v::not(v::undefOr(v::alpha())),
- '',
- ['notUndefOrAlpha' => 'Should not be undefined or alpha'],
- ],
- 'Without subsequent result' => [
- v::undefOr(v::alpha()->stringType()),
- 1234,
- ],
- 'Without subsequent result with templates' => [
- v::undefOr(v::alpha()->stringType()),
- 1234,
- [
- 'undefOrAlpha' => 'Should be nul or alpha',
- 'undefOrStringType' => 'Should be nul or string type',
- ],
- ],
-]);
-?>
---EXPECT--
-Default
-⎺⎺⎺⎺⎺⎺⎺
-1234 must contain only letters (a-z) or must be undefined
-- 1234 must contain only letters (a-z) or must be undefined
-[
- 'undefOrAlpha' => '1234 must contain only letters (a-z) or must be undefined',
-]
-
-Inverted wrapper
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"alpha" must not contain letters (a-z) and must not be undefined
-- "alpha" must not contain letters (a-z) and must not be undefined
-[
- 'notUndefOrAlpha' => '"alpha" must not contain letters (a-z) and must not be undefined',
-]
-
-Inverted wrapped
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-"alpha" must not contain letters (a-z) or must be undefined
-- "alpha" must not contain letters (a-z) or must be undefined
-[
- 'undefOrNotAlpha' => '"alpha" must not contain letters (a-z) or must be undefined',
-]
-
-Inverted undefined
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-`null` must not contain letters (a-z) and must not be undefined
-- `null` must not contain letters (a-z) and must not be undefined
-[
- 'notUndefOrAlpha' => '`null` must not contain letters (a-z) and must not be undefined',
-]
-
-Inverted undefined, wrapped name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapped must not contain letters (a-z) and must not be undefined
-- Wrapped must not contain letters (a-z) and must not be undefined
-[
- 'notUndefOrAlpha' => 'Wrapped must not contain letters (a-z) and must not be undefined',
-]
-
-Inverted undefined, wrapper name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Wrapper must not contain letters (a-z) and must not be undefined
-- Wrapper must not contain letters (a-z) and must not be undefined
-[
- 'notUndefOrAlpha' => 'Wrapper must not contain letters (a-z) and must not be undefined',
-]
-
-Inverted undefined, not name
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not must not contain letters (a-z) and must not be undefined
-- Not must not contain letters (a-z) and must not be undefined
-[
- 'notUndefOrAlpha' => 'Not must not contain letters (a-z) and must not be undefined',
-]
-
-With template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Underneath the undulating umbrella
-- Underneath the undulating umbrella
-[
- 'undefOrAlpha' => 'Underneath the undulating umbrella',
-]
-
-With array template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Undefined number of unique unicorns
-- Undefined number of unique unicorns
-[
- 'undefOrAlpha' => 'Undefined number of unique unicorns',
-]
-
-Inverted undefined with template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Should not be undefined or alpha
-- Should not be undefined or alpha
-[
- 'notUndefOrAlpha' => 'Should not be undefined or alpha',
-]
-
-Without subsequent result
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-1234 must contain only letters (a-z) or must be undefined
-- All of the required rules must pass for 1234
- - 1234 must contain only letters (a-z) or must be undefined
- - 1234 must be a string or must be undefined
-[
- '__root__' => 'All of the required rules must pass for 1234',
- 'undefOrAlpha' => '1234 must contain only letters (a-z) or must be undefined',
- 'undefOrStringType' => '1234 must be a string or must be undefined',
-]
-
-Without subsequent result with templates
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Should be nul or alpha
-- All of the required rules must pass for 1234
- - Should be nul or alpha
- - Should be nul or string type
-[
- '__root__' => 'All of the required rules must pass for 1234',
- 'undefOrAlpha' => 'Should be nul or alpha',
- 'undefOrStringType' => 'Should be nul or string type',
-]
diff --git a/tests/integration/rules/unique.phpt b/tests/integration/rules/unique.phpt
deleted file mode 100644
index 64f921c7..00000000
--- a/tests/integration/rules/unique.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::unique()->assert([1, 2, 2, 3]));
-exceptionMessage(static fn() => v::not(v::unique())->assert([1, 2, 3, 4]));
-exceptionFullMessage(static fn() => v::unique()->assert('test'));
-exceptionFullMessage(static fn() => v::not(v::unique())->assert(['a', 'b', 'c']));
-?>
---EXPECT--
-`[1, 2, 2, 3]` must not contain duplicates
-`[1, 2, 3, 4]` must contain duplicates
-- "test" must not contain duplicates
-- `["a", "b", "c"]` must contain duplicates
diff --git a/tests/integration/rules/uploaded.phpt b/tests/integration/rules/uploaded.phpt
deleted file mode 100644
index 9208f2cc..00000000
--- a/tests/integration/rules/uploaded.phpt
+++ /dev/null
@@ -1,24 +0,0 @@
---FILE--
- v::uploaded()->assert('filename'));
-uopz_set_return('is_uploaded_file', true);
-exceptionMessage(static fn() => v::not(v::uploaded())->assert('filename'));
-uopz_set_return('is_uploaded_file', false);
-exceptionFullMessage(static fn() => v::uploaded()->assert('filename'));
-uopz_set_return('is_uploaded_file', true);
-exceptionFullMessage(static fn() => v::not(v::uploaded())->assert('filename'));?>
---SKIPIF--
-
---EXPECT--
-"filename" must be an uploaded file
-"filename" must not be an uploaded file
-- "filename" must be an uploaded file
-- "filename" must not be an uploaded file
diff --git a/tests/integration/rules/uppercase.phpt b/tests/integration/rules/uppercase.phpt
deleted file mode 100644
index da486d07..00000000
--- a/tests/integration/rules/uppercase.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::uppercase()->assert('lowercase'));
-exceptionFullMessage(static fn() => v::uppercase()->assert('lowercase'));
-exceptionMessage(static fn() => v::not(v::uppercase())->assert('UPPERCASE'));
-exceptionFullMessage(static fn() => v::not(v::uppercase())->assert('UPPERCASE'));
-?>
---EXPECT--
-"lowercase" must contain only uppercase letters
-- "lowercase" must contain only uppercase letters
-"UPPERCASE" must not contain only uppercase letters
-- "UPPERCASE" must not contain only uppercase letters
\ No newline at end of file
diff --git a/tests/integration/rules/url.phpt b/tests/integration/rules/url.phpt
deleted file mode 100644
index 34be58a6..00000000
--- a/tests/integration/rules/url.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::url()->assert('example.com'));
-exceptionMessage(static fn() => v::not(v::url())->assert('http://example.com'));
-exceptionFullMessage(static fn() => v::url()->assert('example.com'));
-exceptionFullMessage(static fn() => v::not(v::url())->assert('http://example.com'));
-?>
---EXPECT--
-"example.com" must be a URL
-"http://example.com" must not be a URL
-- "example.com" must be a URL
-- "http://example.com" must not be a URL
diff --git a/tests/integration/rules/uuid.phpt b/tests/integration/rules/uuid.phpt
deleted file mode 100644
index 4c9f14df..00000000
--- a/tests/integration/rules/uuid.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::uuid()->assert('g71a18f4-3a13-11e7-a919-92ebcb67fe33'));
-exceptionMessage(static fn() => v::uuid(1)->assert('e0b5ffb9-9caf-2a34-9673-8fc91db78be6'));
-exceptionMessage(static fn() => v::not(v::uuid())->assert('fb3a7909-8034-59f5-8f38-21adbc168db7'));
-exceptionMessage(static fn() => v::not(v::uuid(3))->assert('11a38b9a-b3da-360f-9353-a5a725514269'));
-exceptionFullMessage(static fn() => v::uuid()->assert('g71a18f4-3a13-11e7-a919-92ebcb67fe33'));
-exceptionFullMessage(static fn() => v::uuid(4)->assert('a71a18f4-3a13-11e7-a919-92ebcb67fe33'));
-exceptionFullMessage(static fn() => v::not(v::uuid())->assert('e0b5ffb9-9caf-4a34-9673-8fc91db78be6'));
-exceptionFullMessage(static fn() => v::not(v::uuid(5))->assert('c4a760a8-dbcf-5254-a0d9-6a4474bd1b62'));
-?>
---EXPECT--
-"g71a18f4-3a13-11e7-a919-92ebcb67fe33" must be a valid UUID
-"e0b5ffb9-9caf-2a34-9673-8fc91db78be6" must be a valid UUID version 1
-"fb3a7909-8034-59f5-8f38-21adbc168db7" must not be a valid UUID
-"11a38b9a-b3da-360f-9353-a5a725514269" must not be a valid UUID version 3
-- "g71a18f4-3a13-11e7-a919-92ebcb67fe33" must be a valid UUID
-- "a71a18f4-3a13-11e7-a919-92ebcb67fe33" must be a valid UUID version 4
-- "e0b5ffb9-9caf-4a34-9673-8fc91db78be6" must not be a valid UUID
-- "c4a760a8-dbcf-5254-a0d9-6a4474bd1b62" must not be a valid UUID version 5
diff --git a/tests/integration/rules/version.phpt b/tests/integration/rules/version.phpt
deleted file mode 100644
index 6e3261c3..00000000
--- a/tests/integration/rules/version.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::version()->assert('1.3.7--'));
-exceptionMessage(static fn() => v::not(v::version())->assert('1.0.0-alpha'));
-exceptionFullMessage(static fn() => v::version()->assert('1.2.3.4-beta'));
-exceptionFullMessage(static fn() => v::not(v::version())->assert('1.3.7-rc.1'));
-?>
---EXPECT--
-"1.3.7--" must be a version
-"1.0.0-alpha" must not be a version
-- "1.2.3.4-beta" must be a version
-- "1.3.7-rc.1" must not be a version
diff --git a/tests/integration/rules/videoUrl.phpt b/tests/integration/rules/videoUrl.phpt
deleted file mode 100644
index 314795ce..00000000
--- a/tests/integration/rules/videoUrl.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::videoUrl()->assert('example.com'));
-exceptionMessage(static fn() => v::videoUrl('YouTube')->assert('example.com'));
-exceptionMessage(static fn() => v::not(v::videoUrl())->assert('https://player.vimeo.com/video/7178746722'));
-exceptionMessage(static fn() => v::not(v::videoUrl('YouTube'))->assert('https://www.youtube.com/embed/netHLn9TScY'));
-exceptionFullMessage(static fn() => v::videoUrl()->assert('example.com'));
-exceptionFullMessage(static fn() => v::videoUrl('Vimeo')->assert('example.com'));
-exceptionFullMessage(static fn() => v::not(v::videoUrl())->assert('https://youtu.be/netHLn9TScY'));
-exceptionFullMessage(static fn() => v::not(v::videoUrl('Vimeo'))->assert('https://vimeo.com/71787467'));
-?>
---EXPECT--
-"example.com" must be a valid video URL
-"example.com" must be a valid YouTube video URL
-"https://player.vimeo.com/video/7178746722" must not be a valid video URL
-"https://www.youtube.com/embed/netHLn9TScY" must not be a valid YouTube video URL
-- "example.com" must be a valid video URL
-- "example.com" must be a valid Vimeo video URL
-- "https://youtu.be/netHLn9TScY" must not be a valid video URL
-- "https://vimeo.com/71787467" must not be a valid Vimeo video URL
diff --git a/tests/integration/rules/vowel.phpt b/tests/integration/rules/vowel.phpt
deleted file mode 100644
index 61b6831c..00000000
--- a/tests/integration/rules/vowel.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::vowel()->assert('b'));
-exceptionMessage(static fn() => v::vowel('c')->assert('d'));
-exceptionMessage(static fn() => v::not(v::vowel())->assert('a'));
-exceptionMessage(static fn() => v::not(v::vowel('f'))->assert('e'));
-exceptionFullMessage(static fn() => v::vowel()->assert('g'));
-exceptionFullMessage(static fn() => v::vowel('h')->assert('j'));
-exceptionFullMessage(static fn() => v::not(v::vowel())->assert('i'));
-exceptionFullMessage(static fn() => v::not(v::vowel('k'))->assert('o'));
-?>
---EXPECT--
-"b" must consist of vowels only
-"d" must consist of vowels and "c"
-"a" must not consist of vowels only
-"e" must not consist of vowels or "f"
-- "g" must consist of vowels only
-- "j" must consist of vowels and "h"
-- "i" must not consist of vowels only
-- "o" must not consist of vowels or "k"
\ No newline at end of file
diff --git a/tests/integration/rules/when.phpt b/tests/integration/rules/when.phpt
deleted file mode 100644
index 4f610408..00000000
--- a/tests/integration/rules/when.phpt
+++ /dev/null
@@ -1,84 +0,0 @@
---FILE--
- [v::when(v::intVal(), v::positive(), v::notEmpty()), -1],
- 'When invalid use "else"' => [v::when(v::intVal(), v::positive(), v::notEmpty()), ''],
- 'When valid use "then" using single template' => [
- v::when(v::intVal(), v::positive(), v::notEmpty()),
- -1,
- 'That did not go as planned',
- ],
- 'When invalid use "else" using single template' => [
- v::when(v::intVal(), v::positive(), v::notEmpty()),
- '',
- 'That could have been better',
- ],
- 'When valid use "then" using array template' => [
- v::when(v::intVal(), v::positive(), v::notEmpty()),
- -1,
- [
- 'notEmpty' => '--Never shown--',
- 'positive' => 'Not positive',
- ],
- ],
- 'When invalid use "else" using array template' => [
- v::when(v::intVal(), v::positive(), v::notEmpty()),
- '',
- [
- 'notEmpty' => 'Not empty',
- 'positive' => '--Never shown--',
- ],
- ],
-]);
-?>
---EXPECT--
-When valid use "then"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
--1 must be a positive number
-- -1 must be a positive number
-[
- 'positive' => '-1 must be a positive number',
-]
-
-When invalid use "else"
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-The value must not be empty
-- The value must not be empty
-[
- 'notEmpty' => 'The value must not be empty',
-]
-
-When valid use "then" using single template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-That did not go as planned
-- That did not go as planned
-[
- 'positive' => 'That did not go as planned',
-]
-
-When invalid use "else" using single template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-That could have been better
-- That could have been better
-[
- 'notEmpty' => 'That could have been better',
-]
-
-When valid use "then" using array template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not positive
-- Not positive
-[
- 'positive' => 'Not positive',
-]
-
-When invalid use "else" using array template
-⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
-Not empty
-- Not empty
-[
- 'notEmpty' => 'Not empty',
-]
diff --git a/tests/integration/rules/writable.phpt b/tests/integration/rules/writable.phpt
deleted file mode 100644
index 2086426e..00000000
--- a/tests/integration/rules/writable.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::writable()->assert('/path/of/a/valid/writable/file.txt'));
-exceptionMessage(static fn() => v::not(v::writable())->assert('tests/fixtures/valid-image.png'));
-exceptionFullMessage(static fn() => v::writable()->assert([]));
-exceptionFullMessage(static fn() => v::not(v::writable())->assert('tests/fixtures/invalid-image.png'));
-?>
---EXPECT--
-"/path/of/a/valid/writable/file.txt" must be writable
-"tests/fixtures/valid-image.png" must not be writable
-- `[]` must be writable
-- "tests/fixtures/invalid-image.png" must not be writable
diff --git a/tests/integration/rules/xdigit.phpt b/tests/integration/rules/xdigit.phpt
deleted file mode 100644
index 6a5533b6..00000000
--- a/tests/integration/rules/xdigit.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::xdigit()->assert('aaa%a'));
-exceptionMessage(static fn() => v::xdigit(' ')->assert('bbb%b'));
-exceptionMessage(static fn() => v::not(v::xdigit())->assert('ccccc'));
-exceptionMessage(static fn() => v::not(v::xdigit('% '))->assert('ddd%d'));
-exceptionFullMessage(static fn() => v::xdigit()->assert('eee^e'));
-exceptionFullMessage(static fn() => v::not(v::xdigit())->assert('fffff'));
-exceptionFullMessage(static fn() => v::xdigit('* &%')->assert('000^0'));
-exceptionFullMessage(static fn() => v::not(v::xdigit('^'))->assert('111^1'));
-?>
---EXPECT--
-"aaa%a" must only contain hexadecimal digits
-"bbb%b" must contain hexadecimal digits and " "
-"ccccc" must not only contain hexadecimal digits
-"ddd%d" must not contain hexadecimal digits or "% "
-- "eee^e" must only contain hexadecimal digits
-- "fffff" must not only contain hexadecimal digits
-- "000^0" must contain hexadecimal digits and "* &%"
-- "111^1" must not contain hexadecimal digits or "^"
\ No newline at end of file
diff --git a/tests/integration/rules/yes.phpt b/tests/integration/rules/yes.phpt
deleted file mode 100644
index 055d3ee6..00000000
--- a/tests/integration/rules/yes.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---FILE--
- v::not(v::yes())->assert('Yes'));
-exceptionMessage(static fn() => v::yes()->assert('si'));
-exceptionFullMessage(static fn() => v::not(v::yes())->assert('Yes'));
-exceptionFullMessage(static fn() => v::yes()->assert('si'));
-?>
---EXPECT--
-"Yes" must not be similar to "Yes"
-"si" must be similar to "Yes"
-- "Yes" must not be similar to "Yes"
-- "si" must be similar to "Yes"
diff --git a/tests/integration/set_template_with_multiple_validators_should_use_template_as_full_message.phpt b/tests/integration/set_template_with_multiple_validators_should_use_template_as_full_message.phpt
deleted file mode 100644
index 882b8349..00000000
--- a/tests/integration/set_template_with_multiple_validators_should_use_template_as_full_message.phpt
+++ /dev/null
@@ -1,13 +0,0 @@
---FILE--
-between(1, 2)->setTemplate('{{name}} is not tasty')->assert('something');
-});
-?>
---EXPECT--
-- "something" is not tasty
diff --git a/tests/integration/set_template_with_multiple_validators_should_use_template_as_main_message.phpt b/tests/integration/set_template_with_multiple_validators_should_use_template_as_main_message.phpt
deleted file mode 100644
index 3d1c3ea6..00000000
--- a/tests/integration/set_template_with_multiple_validators_should_use_template_as_main_message.phpt
+++ /dev/null
@@ -1,15 +0,0 @@
---TEST--
-setTemplate() with multiple validators should use template as main message
---FILE--
-between(1, 2)->setTemplate('{{name}} is not tasty')->assert('something');
-});
-?>
---EXPECT--
-"something" is not tasty
diff --git a/tests/integration/set_template_with_single_validator_should_use_template_as_main_message.phpt b/tests/integration/set_template_with_single_validator_should_use_template_as_main_message.phpt
deleted file mode 100644
index 7cea4c75..00000000
--- a/tests/integration/set_template_with_single_validator_should_use_template_as_main_message.phpt
+++ /dev/null
@@ -1,17 +0,0 @@
---TEST--
-setTemplate() with single validator should use template as main message
---FILE--
-setTemplate('{{name}} is not tasty');
-
-exceptionMessage(static fn() => $rule->assert('something'));
-exceptionMessage(static fn() => $rule->assert('something'));
-?>
---EXPECT--
-"something" is not tasty
-"something" is not tasty
diff --git a/tests/integration/should_not_overwrite_defined_names.phpt b/tests/integration/should_not_overwrite_defined_names.phpt
deleted file mode 100644
index eb4f0358..00000000
--- a/tests/integration/should_not_overwrite_defined_names.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---FILE--
- 'not an email'];
-
-exceptionMessage(static fn() => v::key('email', v::email()->setName('Email'))->setName('Foo')->assert($input));
-
-// This is a trick thing: the call to `setName()` here seems to be the one
-// from the `Key` rule but it's actually from the `Validator`.
-exceptionMessage(static fn() => v::key('email', v::email())->setName('Email')->assert($input));
-
-exceptionMessage(static fn() => v::key('email', v::email())->assert($input));
-?>
---EXPECT--
-Email must be a valid email address
-email must be a valid email address
-email must be a valid email address
\ No newline at end of file
diff --git a/tests/integration/transformers/aliases.phpt b/tests/integration/transformers/aliases.phpt
deleted file mode 100644
index 0fcb669c..00000000
--- a/tests/integration/transformers/aliases.phpt
+++ /dev/null
@@ -1,17 +0,0 @@
---FILE--
- [v::optional(v::scalarVal()), []],
-]);
-?>
---EXPECT--
-optional
-⎺⎺⎺⎺⎺⎺⎺⎺
-`[]` must be a scalar value or must be undefined
-- `[]` must be a scalar value or must be undefined
-[
- 'undefOrScalarVal' => '`[]` must be a scalar value or must be undefined',
-]
diff --git a/tests/integration/transformers/deprecated_age.phpt b/tests/integration/transformers/deprecated_age.phpt
deleted file mode 100644
index b0f56978..00000000
--- a/tests/integration/transformers/deprecated_age.phpt
+++ /dev/null
@@ -1,41 +0,0 @@
---FILE--
- v::minAge(18)->assert('17 years ago'));
-exceptionMessage(static fn() => v::not(v::minAge(18))->assert('-30 years'));
-exceptionFullMessage(static fn() => v::minAge(18)->assert('yesterday'));
-exceptionFullMessage(static fn() => v::minAge(18, 'd/m/Y')->assert('12/10/2010'));
-exceptionMessage(static fn() => v::maxAge(12)->assert('50 years ago'));
-exceptionMessage(static fn() => v::not(v::maxAge(12))->assert('11 years ago'));
-exceptionFullMessage(static fn() => v::maxAge(12, 'Y-m-d')->assert('1988-09-09'));
-exceptionFullMessage(static fn() => v::not(v::maxAge(12, 'Y-m-d'))->assert('2018-01-01'));
-?>
---EXPECTF--
-
-Deprecated: The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-The number of years between now and 17 years ago must be greater than or equal to 18
-
-Deprecated: The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-The number of years between now and -30 years must be less than 18
-
-Deprecated: The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-- The number of years between now and yesterday must be greater than or equal to 18
-
-Deprecated: The minAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-- The number of years between now and %d/%d/%d must be greater than or equal to 18
-
-Deprecated: The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-The number of years between now and 50 years ago must be less than or equal to 12
-
-Deprecated: The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-The number of years between now and 11 years ago must be greater than 12
-
-Deprecated: The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-- The number of years between now and %d-%d-%d must be less than or equal to 12
-
-Deprecated: The maxAge() rule has been deprecated and will be removed in the next major version. Use dateTimeDiff() instead. %s
-- The number of years between now and %d-%d-%d must be greater than 12
diff --git a/tests/integration/transformers/deprecated_attribute.phpt b/tests/integration/transformers/deprecated_attribute.phpt
deleted file mode 100644
index 42b390f5..00000000
--- a/tests/integration/transformers/deprecated_attribute.phpt
+++ /dev/null
@@ -1,43 +0,0 @@
---FILE--
-foo = true;
-$object->bar = 42;
-
-exceptionMessage(static fn() => v::attribute('baz')->assert($object));
-exceptionMessage(static fn() => v::not(v::attribute('foo'))->assert($object));
-exceptionMessage(static fn() => v::attribute('foo', v::falseVal())->assert($object));
-exceptionMessage(static fn() => v::not(v::attribute('foo', v::trueVal()))->assert($object));
-exceptionMessage(static fn() => v::attribute('foo', v::falseVal(), true)->assert($object));
-exceptionMessage(static fn() => v::not(v::attribute('foo', v::trueVal(), true))->assert($object));
-exceptionMessage(static fn() => v::attribute('foo', v::falseVal(), false)->assert($object));
-exceptionMessage(static fn() => v::not(v::attribute('foo', v::trueVal(), false))->assert($object));
-?>
---EXPECTF--
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use propertyExists() instead. %s
-baz must be present
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use propertyExists() instead. %s
-foo must not be present
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead. %s
-foo must evaluate to `false`
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use property() instead. %s
-foo must not evaluate to `true`
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use property() without it. %s
-foo must evaluate to `false`
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use property() without it. %s
-foo must not evaluate to `true`
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use propertyOptional() instead. %s
-foo must evaluate to `false`
-
-Deprecated: The attribute() rule has been deprecated and will be removed in the next major version. Use propertyOptional() instead. %s
-foo must not evaluate to `true`
diff --git a/tests/integration/transformers/deprecated_key.phpt b/tests/integration/transformers/deprecated_key.phpt
deleted file mode 100644
index 63e87168..00000000
--- a/tests/integration/transformers/deprecated_key.phpt
+++ /dev/null
@@ -1,33 +0,0 @@
---FILE--
- true, 'bar' => 42];
-
-exceptionMessage(static fn() => v::key('baz')->assert($array));
-exceptionMessage(static fn() => v::not(v::key('foo'))->assert($array));
-exceptionMessage(static fn() => v::key('foo', v::falseVal(), true)->assert($array));
-exceptionMessage(static fn() => v::not(v::key('foo', v::trueVal(), true))->assert($array));
-exceptionMessage(static fn() => v::key('foo', v::falseVal(), false)->assert($array));
-exceptionMessage(static fn() => v::not(v::key('foo', v::trueVal(), false))->assert($array));
-?>
---EXPECTF--
-
-Deprecated: Calling key() without a second parameter has been deprecated, and will be not be allowed in the next major version. Use keyExists() instead. %s
-baz must be present
-
-Deprecated: Calling key() without a second parameter has been deprecated, and will be not be allowed in the next major version. Use keyExists() instead. %s
-foo must not be present
-
-Deprecated: Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use key() without it the third parameter. %s
-foo must evaluate to `false`
-
-Deprecated: Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use key() without it the third parameter. %s
-foo must not evaluate to `true`
-
-Deprecated: Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use keyOptional() instead. %s
-foo must evaluate to `false`
-
-Deprecated: Calling key() with a third parameter has been deprecated, and will be not be allowed in the next major version. Use keyOptional() instead. %s
-foo must not evaluate to `true`
diff --git a/tests/integration/transformers/deprecated_keyNested.phpt b/tests/integration/transformers/deprecated_keyNested.phpt
deleted file mode 100644
index adc540a7..00000000
--- a/tests/integration/transformers/deprecated_keyNested.phpt
+++ /dev/null
@@ -1,45 +0,0 @@
---FILE--
- (object) [
- 'bar' => 123,
- ],
-];
-
-exceptionMessage(static fn() => v::keyNested('foo.bar.baz')->assert(['foo.bar.baz' => false]));
-exceptionMessage(static fn() => v::keyNested('foo.bar')->assert($input));
-exceptionMessage(static fn() => v::keyNested('foo.bar')->assert(new ArrayObject($input)));
-exceptionMessage(static fn() => v::keyNested('foo.bar', v::negative())->assert($input));
-exceptionMessage(static fn() => v::keyNested('foo.bar')->assert($input));
-exceptionMessage(static fn() => v::keyNested('foo.bar', v::stringType())->assert($input));
-exceptionMessage(static fn() => v::keyNested('foo.bar.baz', v::notEmpty(), false)->assert($input));
-exceptionMessage(static fn() => v::keyNested('foo.bar', v::floatType(), false)->assert($input));
-?>
---EXPECTF--
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-foo must be present
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-No exception was thrown
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-No exception was thrown
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-bar must be a negative number
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-No exception was thrown
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-bar must be a string
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-No exception was thrown
-
-Deprecated: The keyNested() rule is deprecated and will be removed in the next major version. Use nested key() or property() instead. %s
-bar must be float
diff --git a/tests/integration/transformers/deprecated_keyValue.phpt b/tests/integration/transformers/deprecated_keyValue.phpt
deleted file mode 100644
index 7b4d9f38..00000000
--- a/tests/integration/transformers/deprecated_keyValue.phpt
+++ /dev/null
@@ -1,47 +0,0 @@
---FILE--
- v::keyValue('foo', 'equals', 'bar')->assert(['bar' => 42]));
-exceptionMessage(static fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 42]));
-exceptionMessage(static fn() => v::keyValue('foo', 'json', 'bar')->assert(['foo' => 42, 'bar' => 43]));
-exceptionMessage(static fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 1, 'bar' => 2]));
-exceptionMessage(static fn() => v::not(v::keyValue('foo', 'equals', 'bar'))->assert(['foo' => 1, 'bar' => 1]));
-exceptionFullMessage(static fn() => v::keyValue('foo', 'equals', 'bar')->assert(['bar' => 42]));
-exceptionFullMessage(static fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 42]));
-exceptionFullMessage(static fn() => v::keyValue('foo', 'json', 'bar')->assert(['foo' => 42, 'bar' => 43]));
-exceptionFullMessage(static fn() => v::keyValue('foo', 'equals', 'bar')->assert(['foo' => 1, 'bar' => 2]));
-exceptionFullMessage(static fn() => v::not(v::keyValue('foo', 'equals', 'bar'))->assert(['foo' => 1, 'bar' => 1]));
-?>
---EXPECTF--
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-foo must be present
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-bar must be present
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-bar must be valid to validate foo
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-foo must be equal to 2
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-foo must not be equal to 1
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-- foo must be present
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-- bar must be present
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-- bar must be valid to validate foo
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-- foo must be equal to 2
-
-Deprecated: The keyValue() rule has been deprecated and will be removed in the next major version. Use nested lazy() instead. %s
-- foo must not be equal to 1
diff --git a/tests/integration/transformers/deprecated_length.phpt b/tests/integration/transformers/deprecated_length.phpt
deleted file mode 100644
index de9d82cd..00000000
--- a/tests/integration/transformers/deprecated_length.phpt
+++ /dev/null
@@ -1,103 +0,0 @@
---FILE--
- v::length(0, 5, false)->assert('forest'));
-exceptionMessage(static fn() => v::length(10, 20)->assert('river'));
-exceptionMessage(static fn() => v::length(15, null, false)->assert('mountain'));
-exceptionMessage(static fn() => v::length(20)->assert('ocean'));
-exceptionMessage(static fn() => v::length(2, 5)->assert('desert'));
-exceptionMessage(static fn() => v::not(v::length(0, 15))->assert('rainforest'));
-exceptionMessage(static fn() => v::not(v::length(0, 20, false))->assert('glacier'));
-exceptionMessage(static fn() => v::not(v::length(3, null))->assert('meadow'));
-exceptionMessage(static fn() => v::not(v::length(5, null, false))->assert('volcano'));
-exceptionMessage(static fn() => v::not(v::length(5, 20))->assert('canyon'));
-exceptionFullMessage(static fn() => v::length(0, 5, false)->assert('prairie'));
-exceptionFullMessage(static fn() => v::length(0, 5)->assert('wetland'));
-exceptionFullMessage(static fn() => v::length(15, null, false)->assert('tundra'));
-exceptionFullMessage(static fn() => v::length(20)->assert('savanna'));
-exceptionFullMessage(static fn() => v::length(7, 10)->assert('marsh'));
-exceptionFullMessage(static fn() => v::length(4, 10, false)->assert('reef'));
-exceptionFullMessage(static fn() => v::not(v::length(0, 15))->assert('valley'));
-exceptionFullMessage(static fn() => v::not(v::length(0, 20, false))->assert('island'));
-exceptionFullMessage(static fn() => v::not(v::length(5, null))->assert('plateau'));
-exceptionFullMessage(static fn() => v::not(v::length(3, null, false))->assert('fjord'));
-exceptionFullMessage(static fn() => v::not(v::length(5, 20))->assert('delta'));
-exceptionFullMessage(static fn() => v::not(v::length(5, 11, false))->assert('waterfall'));
-exceptionFullMessage(static fn() => v::length(8, 8)->assert('estuary'));
-exceptionFullMessage(static fn() => v::not(v::length(5, 5))->assert('grove'));
-?>
---EXPECTF--
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(5) instead. %s
-The length of "forest" must be less than 5
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(10, 20) instead. %s
-The length of "river" must be between 10 and 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(15) instead. %s
-The length of "mountain" must be greater than 15
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(20) instead. %s
-The length of "ocean" must be greater than or equal to 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(2, 5) instead. %s
-The length of "desert" must be between 2 and 5
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThanOrEqual(15) instead. %s
-The length of "rainforest" must be greater than 15
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(20) instead. %s
-The length of "glacier" must not be less than 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(3) instead. %s
-The length of "meadow" must be less than 3
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(5) instead. %s
-The length of "volcano" must not be greater than 5
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(5, 20) instead. %s
-The length of "canyon" must not be between 5 and 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(5) instead. %s
-- The length of "prairie" must be less than 5
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThanOrEqual(5) instead. %s
-- The length of "wetland" must be less than or equal to 5
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(15) instead. %s
-- The length of "tundra" must be greater than 15
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(20) instead. %s
-- The length of "savanna" must be greater than or equal to 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(7, 10) instead. %s
-- The length of "marsh" must be between 7 and 10
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetweenExclusive(4, 10) instead. %s
-- The length of "reef" must be greater than 4 and less than 10
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThanOrEqual(15) instead. %s
-- The length of "valley" must be greater than 15
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthLessThan(20) instead. %s
-- The length of "island" must not be less than 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThanOrEqual(5) instead. %s
-- The length of "plateau" must be less than 5
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthGreaterThan(3) instead. %s
-- The length of "fjord" must not be greater than 3
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetween(5, 20) instead. %s
-- The length of "delta" must not be between 5 and 20
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthBetweenExclusive(5, 11) instead. %s
-- The length of "waterfall" must not be greater than 5 or less than 11
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthEquals(8) instead. %s
-- The length of "estuary" must be equal to 8
-
-Deprecated: Calling length() with scalar values has been deprecated, and will not be allowed in the next major version. Use lengthEquals(5) instead. %s
-- The length of "grove" must not be equal to 5
diff --git a/tests/integration/transformers/deprecated_max.phpt b/tests/integration/transformers/deprecated_max.phpt
deleted file mode 100644
index 26cc6afd..00000000
--- a/tests/integration/transformers/deprecated_max.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::max(10)->assert(11));
-exceptionMessage(static fn() => v::not(v::max(10))->assert(5));
-exceptionFullMessage(static fn() => v::max('today')->assert('tomorrow'));
-exceptionFullMessage(static fn() => v::not(v::max('b'))->assert('a'));
-?>
---EXPECTF--
-
-Deprecated: Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead. %s
-11 must be less than or equal to 10
-
-Deprecated: Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead. %s
-5 must be greater than 10
-
-Deprecated: Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead. %s
-- "tomorrow" must be less than or equal to "today"
-
-Deprecated: Calling max() with a scalar value has been deprecated, and will be not allows in the next major version. Use lessThanOrEqual() instead. %s
-- "a" must be greater than "b"
diff --git a/tests/integration/transformers/deprecated_min.phpt b/tests/integration/transformers/deprecated_min.phpt
deleted file mode 100644
index 06863e0b..00000000
--- a/tests/integration/transformers/deprecated_min.phpt
+++ /dev/null
@@ -1,23 +0,0 @@
---FILE--
- v::min(INF)->assert(10));
-exceptionMessage(static fn() => v::not(v::min(5))->assert(INF));
-exceptionFullMessage(static fn() => v::min('today')->assert('yesterday'));
-exceptionFullMessage(static fn() => v::not(v::min('a'))->assert('z'));
-?>
---EXPECTF--
-
-Deprecated: Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead. %s
-10 must be greater than or equal to `INF`
-
-Deprecated: Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead. %s
-`INF` must be less than 5
-
-Deprecated: Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead. %s
-- "yesterday" must be greater than or equal to "today"
-
-Deprecated: Calling min() with a scalar value has been deprecated, and will be not allows in the next major version. Use greaterThanOrEqual() instead. %s
-- "z" must be less than "a"
diff --git a/tests/integration/transformers/deprecated_type.phpt b/tests/integration/transformers/deprecated_type.phpt
deleted file mode 100644
index ab2dffe9..00000000
--- a/tests/integration/transformers/deprecated_type.phpt
+++ /dev/null
@@ -1,55 +0,0 @@
---FILE--
- v::type('array')->assert(1));
-exceptionMessage(static fn() => v::type('bool')->assert(1));
-exceptionMessage(static fn() => v::type('boolean')->assert(1));
-exceptionMessage(static fn() => v::type('callable')->assert(1));
-exceptionMessage(static fn() => v::type('double')->assert(1));
-exceptionMessage(static fn() => v::type('float')->assert(1));
-exceptionMessage(static fn() => v::type('int')->assert('1'));
-exceptionMessage(static fn() => v::type('integer')->assert('1'));
-exceptionMessage(static fn() => v::type('null')->assert(1));
-exceptionMessage(static fn() => v::type('object')->assert(1));
-exceptionMessage(static fn() => v::type('resource')->assert(1));
-exceptionMessage(static fn() => v::type('string')->assert(1));
-?>
---EXPECTF--
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use arrayType() instead. %s
-1 must be an array
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use boolType() instead. %s
-1 must be a boolean
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use boolType() instead. %s
-1 must be a boolean
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use callableType() instead. %s
-1 must be a callable
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use floatType() instead. %s
-1 must be float
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use floatType() instead. %s
-1 must be float
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use intType() instead. %s
-"1" must be an integer
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use intType() instead. %s
-"1" must be an integer
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use nullType() instead. %s
-1 must be null
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use objectType() instead. %s
-1 must be an object
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use resourceType() instead. %s
-1 must be a resource
-
-Deprecated: The type() rule is deprecated and will be removed in the next major version. Use stringType() instead. %s
-1 must be a string
diff --git a/tests/integration/transformers/prefix.phpt b/tests/integration/transformers/prefix.phpt
deleted file mode 100644
index 746b963a..00000000
--- a/tests/integration/transformers/prefix.phpt
+++ /dev/null
@@ -1,80 +0,0 @@
---FILE--
- [v::keyEquals('foo', 12), ['foo' => 10]],
- 'length' => [v::lengthGreaterThan(3), 'foo'],
- 'max' => [v::maxOdd(), [1, 2, 3, 4]],
- 'min' => [v::minEven(), [1, 2, 3]],
- 'not' => [v::notBetween(1, 3), 2],
- 'nullOr' => [v::nullOrBoolType(), 'string'],
- 'property' => [v::propertyBetween('foo', 1, 3), (object) ['foo' => 5]],
- 'undefOr' => [v::undefOrUrl(), 'string'],
-]);
-?>
---EXPECT--
-key
-⎺⎺⎺
-foo must be equal to 12
-- foo must be equal to 12
-[
- 'foo' => 'foo must be equal to 12',
-]
-
-length
-⎺⎺⎺⎺⎺⎺
-The length of "foo" must be greater than 3
-- The length of "foo" must be greater than 3
-[
- 'lengthGreaterThan' => 'The length of "foo" must be greater than 3',
-]
-
-max
-⎺⎺⎺
-As the maximum of `[1, 2, 3, 4]`, 4 must be an odd number
-- As the maximum of `[1, 2, 3, 4]`, 4 must be an odd number
-[
- 'maxOdd' => 'As the maximum of `[1, 2, 3, 4]`, 4 must be an odd number',
-]
-
-min
-⎺⎺⎺
-As the minimum from `[1, 2, 3]`, 1 must be an even number
-- As the minimum from `[1, 2, 3]`, 1 must be an even number
-[
- 'minEven' => 'As the minimum from `[1, 2, 3]`, 1 must be an even number',
-]
-
-not
-⎺⎺⎺
-2 must not be between 1 and 3
-- 2 must not be between 1 and 3
-[
- 'notBetween' => '2 must not be between 1 and 3',
-]
-
-nullOr
-⎺⎺⎺⎺⎺⎺
-"string" must be a boolean or must be null
-- "string" must be a boolean or must be null
-[
- 'nullOrBoolType' => '"string" must be a boolean or must be null',
-]
-
-property
-⎺⎺⎺⎺⎺⎺⎺⎺
-foo must be between 1 and 3
-- foo must be between 1 and 3
-[
- 'foo' => 'foo must be between 1 and 3',
-]
-
-undefOr
-⎺⎺⎺⎺⎺⎺⎺
-"string" must be a URL or must be undefined
-- "string" must be a URL or must be undefined
-[
- 'undefOrUrl' => '"string" must be a URL or must be undefined',
-]
diff --git a/tests/integration/translator.phpt b/tests/integration/translator.phpt
deleted file mode 100644
index e7506840..00000000
--- a/tests/integration/translator.phpt
+++ /dev/null
@@ -1,31 +0,0 @@
---FILE--
- 'Todas as regras requeridas devem passar para {{name}}',
- 'The length of' => 'O comprimento de',
- '{{name}} must be of type string' => '{{name}} deve ser do tipo string',
- '{{name}} must be between {{minValue}} and {{maxValue}}' => '{{name}} deve possuir de {{minValue}} a {{maxValue}} caracteres',
- '{{name}} must be a valid telephone number for country {{countryName|trans}}'
- => '{{name}} deve ser um número de telefone válido para o país {{countryName|trans}}',
- 'United States' => 'Estados Unidos',
- 'years' => 'anos',
- 'The number of {{type|trans}} between now and' => 'O número de {{type|trans}} entre agora e',
- '{{name}} must be equal to {{compareTo}}' => '{{name}} deve ser igual a {{compareTo}}',
-]));
-
-exceptionFullMessage(static fn() => Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert(0));
-exceptionMessage(static fn() => v::dateTimeDiff('years', v::equals(2))->assert('1972-02-09'));
-?>
---EXPECT--
-- Todas as regras requeridas devem passar para 0
- - 0 must be a string
- - O comprimento de 0 deve possuir de 2 a 15 caracteres
- - 0 deve ser um número de telefone válido para o país Estados Unidos
-O número de anos entre agora e 1972-02-09 deve ser igual a 2
diff --git a/tests/unit/Message/StandardFormatter/FullProvider.php b/tests/unit/Message/StandardFormatter/FullProvider.php
index 9c7a1b5d..f92cfc4f 100644
--- a/tests/unit/Message/StandardFormatter/FullProvider.php
+++ b/tests/unit/Message/StandardFormatter/FullProvider.php
@@ -31,21 +31,21 @@ trait FullProvider
],
'with single-level children, without templates' => [
self::singleLevelChildrenMessage(),
- << [
self::singleLevelChildrenMessage(),
- << [
'__root__' => 'Parent custom',
@@ -57,12 +57,12 @@ trait FullProvider
],
'with single-level children, with partial templates' => [
self::singleLevelChildrenMessage(),
- << [
'1st' => '1st custom',
@@ -79,23 +79,23 @@ trait FullProvider
],
'with single-nested child, without templates' => [
self::multiLevelChildrenWithSingleNestedChildMessage(),
- << [
self::multiLevelChildrenWithSingleNestedChildMessage(),
- << 1st custom
- 3rd custom
- MESSAGE,
+ FULL_MESSAGE,
[
'parent' => [
'__root__' => 'Parent custom',
@@ -110,13 +110,13 @@ trait FullProvider
],
'with single-nested child, with partial templates' => [
self::multiLevelChildrenWithSingleNestedChildMessage(),
- << [
'__root__' => 'Parent custom',
@@ -130,12 +130,12 @@ trait FullProvider
],
'with single-nested child, with overwritten templates' => [
self::multiLevelChildrenWithSingleNestedChildMessage(),
- << [
'__root__' => 'Parent custom',
@@ -147,25 +147,25 @@ trait FullProvider
],
'with multi-nested children, without templates' => [
self::multiLevelChildrenWithMultiNestedChildrenMessage(),
- << [
self::multiLevelChildrenWithMultiNestedChildrenMessage(),
- << 1st custom
- 2nd > 2nd custom
- 3rd custom
- MESSAGE,
+ FULL_MESSAGE,
[
'parent' => [
'__root__' => 'Parent custom',
@@ -181,14 +181,14 @@ trait FullProvider
],
'with multi-nested children, with partial templates' => [
self::multiLevelChildrenWithMultiNestedChildrenMessage(),
- << 2nd custom
- 3rd custom
- MESSAGE,
+ FULL_MESSAGE,
[
'parent' => [
'__root__' => 'Parent custom',
@@ -202,12 +202,12 @@ trait FullProvider
],
'with multi-nested children, with overwritten templates' => [
self::multiLevelChildrenWithMultiNestedChildrenMessage(),
- << [
'__root__' => 'Parent custom',
@@ -219,21 +219,21 @@ trait FullProvider
],
'with children with the same id, without templates' => [
self::singleLevelChildrenWithSameId(),
- << [
self::singleLevelChildrenWithSameId(),
- << [
'__root__' => 'Parent custom',
@@ -245,12 +245,12 @@ trait FullProvider
],
'with children with the same id, with partial templates' => [
self::singleLevelChildrenWithSameId(),
- << [
'child.1' => '1st custom',
@@ -260,22 +260,22 @@ trait FullProvider
],
'with siblings that dot not have only one child' => [
self::multiLevelChildrenWithSiblingsThatHaveOnlyOneChild(),
- << [
self::multiLevelChildrenWithSiblingsThatHaveOnlyOneChild(),
- << 1st custom
- MESSAGE,
+ FULL_MESSAGE,
[
'parent' => [
'1st' => '1st custom',
@@ -288,7 +288,7 @@ trait FullProvider
],
'with siblings that dot not have more than one child' => [
self::multiLevelChildrenWithSiblingsThatHaveMoreThanOneChild(),
- <<