From 6992e89843cd63f4dbf144ddf773441121241b80 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Fri, 13 Dec 2024 01:16:31 +0100 Subject: [PATCH 1/4] Move all regression tests into a specific folder That way, it will be easier to look for those tests, and the root directory will be cleaner. --- tests/integration/{issue-1033.phpt => issues/1033.phpt} | 0 tests/integration/{issue-1244.phpt => issues/1244.phpt} | 0 tests/integration/{issue-1289.phpt => issues/1289.phpt} | 0 tests/integration/{issue-1333.phpt => issues/1333.phpt} | 0 tests/integration/{issue-1348.phpt => issues/1348.phpt} | 0 tests/integration/{issue-1376.phpt => issues/1376.phpt} | 0 tests/integration/{issue-179.phpt => issues/179.phpt} | 0 tests/integration/{issue-425.phpt => issues/425.phpt} | 0 tests/integration/{issue-446.phpt => issues/446.phpt} | 0 tests/integration/{issue-619.phpt => issues/619.phpt} | 0 tests/integration/{issue-739.phpt => issues/739.phpt} | 0 tests/integration/{issue-796.phpt => issues/796.phpt} | 0 tests/integration/{issue-799.phpt => issues/799.phpt} | 0 tests/integration/{issue-805.phpt => issues/805.phpt} | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename tests/integration/{issue-1033.phpt => issues/1033.phpt} (100%) rename tests/integration/{issue-1244.phpt => issues/1244.phpt} (100%) rename tests/integration/{issue-1289.phpt => issues/1289.phpt} (100%) rename tests/integration/{issue-1333.phpt => issues/1333.phpt} (100%) rename tests/integration/{issue-1348.phpt => issues/1348.phpt} (100%) rename tests/integration/{issue-1376.phpt => issues/1376.phpt} (100%) rename tests/integration/{issue-179.phpt => issues/179.phpt} (100%) rename tests/integration/{issue-425.phpt => issues/425.phpt} (100%) rename tests/integration/{issue-446.phpt => issues/446.phpt} (100%) rename tests/integration/{issue-619.phpt => issues/619.phpt} (100%) rename tests/integration/{issue-739.phpt => issues/739.phpt} (100%) rename tests/integration/{issue-796.phpt => issues/796.phpt} (100%) rename tests/integration/{issue-799.phpt => issues/799.phpt} (100%) rename tests/integration/{issue-805.phpt => issues/805.phpt} (100%) diff --git a/tests/integration/issue-1033.phpt b/tests/integration/issues/1033.phpt similarity index 100% rename from tests/integration/issue-1033.phpt rename to tests/integration/issues/1033.phpt diff --git a/tests/integration/issue-1244.phpt b/tests/integration/issues/1244.phpt similarity index 100% rename from tests/integration/issue-1244.phpt rename to tests/integration/issues/1244.phpt diff --git a/tests/integration/issue-1289.phpt b/tests/integration/issues/1289.phpt similarity index 100% rename from tests/integration/issue-1289.phpt rename to tests/integration/issues/1289.phpt diff --git a/tests/integration/issue-1333.phpt b/tests/integration/issues/1333.phpt similarity index 100% rename from tests/integration/issue-1333.phpt rename to tests/integration/issues/1333.phpt diff --git a/tests/integration/issue-1348.phpt b/tests/integration/issues/1348.phpt similarity index 100% rename from tests/integration/issue-1348.phpt rename to tests/integration/issues/1348.phpt diff --git a/tests/integration/issue-1376.phpt b/tests/integration/issues/1376.phpt similarity index 100% rename from tests/integration/issue-1376.phpt rename to tests/integration/issues/1376.phpt diff --git a/tests/integration/issue-179.phpt b/tests/integration/issues/179.phpt similarity index 100% rename from tests/integration/issue-179.phpt rename to tests/integration/issues/179.phpt diff --git a/tests/integration/issue-425.phpt b/tests/integration/issues/425.phpt similarity index 100% rename from tests/integration/issue-425.phpt rename to tests/integration/issues/425.phpt diff --git a/tests/integration/issue-446.phpt b/tests/integration/issues/446.phpt similarity index 100% rename from tests/integration/issue-446.phpt rename to tests/integration/issues/446.phpt diff --git a/tests/integration/issue-619.phpt b/tests/integration/issues/619.phpt similarity index 100% rename from tests/integration/issue-619.phpt rename to tests/integration/issues/619.phpt diff --git a/tests/integration/issue-739.phpt b/tests/integration/issues/739.phpt similarity index 100% rename from tests/integration/issue-739.phpt rename to tests/integration/issues/739.phpt diff --git a/tests/integration/issue-796.phpt b/tests/integration/issues/796.phpt similarity index 100% rename from tests/integration/issue-796.phpt rename to tests/integration/issues/796.phpt diff --git a/tests/integration/issue-799.phpt b/tests/integration/issues/799.phpt similarity index 100% rename from tests/integration/issue-799.phpt rename to tests/integration/issues/799.phpt diff --git a/tests/integration/issue-805.phpt b/tests/integration/issues/805.phpt similarity index 100% rename from tests/integration/issue-805.phpt rename to tests/integration/issues/805.phpt From 50323d4511203cca0168fbe3616123956c0b87dd Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Fri, 13 Dec 2024 01:30:00 +0100 Subject: [PATCH 2/4] Improve expectations of regression tests With the "exceptionAll()" function, we can then have the single message, the full message, and the messages as an array. That makes it harder to get those bugs back. --- tests/integration/issues/1033.phpt | 16 +++++++++++++-- tests/integration/issues/1244.phpt | 9 +++++++- tests/integration/issues/1289.phpt | 18 +++++++++++++--- tests/integration/issues/1333.phpt | 13 ++++++++++-- tests/integration/issues/1348.phpt | 33 +++++++++++++++++++++++++----- tests/integration/issues/1376.phpt | 18 ++++++++++++++-- tests/integration/issues/179.phpt | 12 +++++++++-- tests/integration/issues/425.phpt | 17 +++++++++++++-- tests/integration/issues/446.phpt | 7 ++++++- tests/integration/issues/619.phpt | 11 +++++++++- tests/integration/issues/739.phpt | 13 ++++++++++-- tests/integration/issues/796.phpt | 12 +++++++++-- tests/integration/issues/799.phpt | 22 ++++++++++++++++++-- tests/integration/issues/805.phpt | 12 ++++++++--- 14 files changed, 183 insertions(+), 30 deletions(-) diff --git a/tests/integration/issues/1033.phpt b/tests/integration/issues/1033.phpt index 9a09cb4df..b9bfa44d6 100644 --- a/tests/integration/issues/1033.phpt +++ b/tests/integration/issues/1033.phpt @@ -3,10 +3,22 @@ require 'vendor/autoload.php'; -exceptionFullMessage(static fn() => v::each(v::equals(1))->assert(['A', 'B', 'B'])); +exceptionAll( + 'https://github.com/Respect/Validation/issues/1033', + static fn() => 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 \ No newline at end of file + - "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 index ff6c8befb..40674dc5f 100644 --- a/tests/integration/issues/1244.phpt +++ b/tests/integration/issues/1244.phpt @@ -3,9 +3,16 @@ require 'vendor/autoload.php'; -exceptionMessages(static fn () => v::key('firstname', v::notBlank()->setName('First Name'))->assert([])); +exceptionAll( + 'https://github.com/Respect/Validation/issues/1244', + static fn () => 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 index 6bd0063c1..d5341202c 100644 --- a/tests/integration/issues/1289.phpt +++ b/tests/integration/issues/1289.phpt @@ -41,13 +41,25 @@ $input = [ 'children' => ['nope'], ], ]; -exceptionMessage(static fn() => $validator->assert($input)); -exceptionFullMessage(static fn() => $validator->assert($input)); +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 \ No newline at end of file + - 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 index a4b42e7fa..ffd0c377e 100644 --- a/tests/integration/issues/1333.phpt +++ b/tests/integration/issues/1333.phpt @@ -3,11 +3,20 @@ require 'vendor/autoload.php'; -exceptionMessages(static fn() => v::noWhitespace()->email()->setName('User Email')->assert('not email')); +exceptionAll( + 'https://github.com/Respect/Validation/issues/1333', + static fn() => 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', -] \ No newline at end of file +] diff --git a/tests/integration/issues/1348.phpt b/tests/integration/issues/1348.phpt index 79e361ad8..166a07391 100644 --- a/tests/integration/issues/1348.phpt +++ b/tests/integration/issues/1348.phpt @@ -12,8 +12,9 @@ $cars = [ ['manufacturer' => 'Honda', 'model' => 'not valid'], ]; -exceptionMessages(static function () use ($cars): void { - Validator::arrayType()->each( +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'])), @@ -22,10 +23,32 @@ exceptionMessages(static function () use ($cars): void { Validator::key('manufacturer', Validator::equals('Ford')) ->key('model', Validator::in(['F150', 'Bronco'])) ) - )->assert($cars); -}); + )->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', @@ -58,4 +81,4 @@ exceptionMessages(static function () use ($cars): void { ], ], ], -] \ No newline at end of file +] diff --git a/tests/integration/issues/1376.phpt b/tests/integration/issues/1376.phpt index db50edac8..cf7106102 100644 --- a/tests/integration/issues/1376.phpt +++ b/tests/integration/issues/1376.phpt @@ -3,7 +3,7 @@ require 'vendor/autoload.php'; -exceptionFullMessage(static function (): void { +exceptionAll('https://github.com/Respect/Validation/issues/1376', static function (): void { v::property('title', v::lengthBetween(2, 3)->stringType()) ->property('description', v::stringType()) ->property('author', v::intType()->lengthBetween(1, 2)) @@ -13,10 +13,24 @@ exceptionFullMessage(static function (): void { ?> --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 \ No newline at end of file + - 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/179.phpt b/tests/integration/issues/179.phpt index 19b84733b..83438cd6a 100644 --- a/tests/integration/issues/179.phpt +++ b/tests/integration/issues/179.phpt @@ -16,8 +16,16 @@ $validator->key('user', v::stringType()); $validator->key('password', v::stringType()); $validator->key('schema', v::stringType()); -exceptionFullMessage(static fn() => $validator->assert($config)); +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 - host must be a string -- user must be present \ No newline at end of file +- 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 index 499e229c7..0d3d2d4ef 100644 --- a/tests/integration/issues/425.phpt +++ b/tests/integration/issues/425.phpt @@ -7,9 +7,22 @@ $validator = v::create() ->key('age', v::intType()->notEmpty()->noneOf(v::stringType(), v::arrayType())) ->key('reference', v::stringType()->notEmpty()->lengthBetween(1, 50)); -exceptionFullMessage(static fn() => $validator->assert(['age' => 1])); -exceptionFullMessage(static fn() => $validator->assert(['reference' => 'QSF1234'])); +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 index 57e3afaa6..cfc2ce7ef 100644 --- a/tests/integration/issues/446.phpt +++ b/tests/integration/issues/446.phpt @@ -8,7 +8,7 @@ $arr = [ 'email' => 'hello@hello.com', ]; -exceptionMessages(static function () use ($arr): void { +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()) @@ -16,6 +16,11 @@ exceptionMessages(static function () use ($arr): void { }); ?> --EXPECT-- +https://github.com/Respect/Validation/issues/446 +⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +The length of name must be between 2 and 32 +- These rules must pass for `["name": "w", "email": "hello@hello.com"]` + - 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 index 8a29506bb..228cde371 100644 --- a/tests/integration/issues/619.phpt +++ b/tests/integration/issues/619.phpt @@ -3,7 +3,16 @@ require 'vendor/autoload.php'; -exceptionMessage(static fn() => v::instance(stdClass::class)->setTemplate('invalid object')->assert('test')); +exceptionAll( + 'https://github.com/Respect/Validation/issues/619', + static fn() => 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 index 54cdbb034..629dfe1aa 100644 --- a/tests/integration/issues/739.phpt +++ b/tests/integration/issues/739.phpt @@ -3,7 +3,16 @@ require 'vendor/autoload.php'; -exceptionMessage(static fn() => v::when(v::alwaysInvalid(), v::alwaysValid())->assert('foo')); +exceptionAll( + 'https://github.com/Respect/Validation/issues/739', + static fn() => v::when(v::alwaysInvalid(), v::alwaysValid())->assert('foo') +); ?> --EXPECT-- -"foo" is invalid \ No newline at end of file +https://github.com/Respect/Validation/issues/739 +⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +"foo" is invalid +- "foo" is invalid +[ + 'alwaysInvalid' => '"foo" is invalid', +] diff --git a/tests/integration/issues/796.phpt b/tests/integration/issues/796.phpt index cead346dc..fd5bce7b2 100644 --- a/tests/integration/issues/796.phpt +++ b/tests/integration/issues/796.phpt @@ -3,7 +3,7 @@ require 'vendor/autoload.php'; -exceptionFullMessage(static function (): void { +exceptionAll('https://github.com/Respect/Validation/issues/796', static function (): void { v::create() ->key( 'mysql', @@ -39,8 +39,16 @@ exceptionFullMessage(static function (): void { }); ?> --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 \ No newline at end of file + - 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', +] diff --git a/tests/integration/issues/799.phpt b/tests/integration/issues/799.phpt index 2e1b1daa1..ad326e6b4 100644 --- a/tests/integration/issues/799.phpt +++ b/tests/integration/issues/799.phpt @@ -7,7 +7,7 @@ use Respect\Validation\Test\Stubs\CountableStub; $input = 'http://www.google.com/search?q=respect.github.com'; -exceptionMessage(static function () use ($input): void { +exceptionAll('https://github.com/Respect/Validation/issues/799', static function () use ($input): void { v::create() ->call( [new CountableStub(1), 'count'], @@ -16,7 +16,7 @@ exceptionMessage(static function () use ($input): void { ->assert($input); }); -exceptionMessage(static function () use ($input): void { +exceptionAll('https://github.com/Respect/Validation/issues/799', static function () use ($input): void { v::create() ->call( static function ($url) { @@ -28,5 +28,23 @@ exceptionMessage(static function () use ($input): void { }); ?> --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" +- These rules must pass for `["scheme": "http", "host": "www.google.com", "path": "/search", "query": "q=respect.github.com"]` + - 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 index 98acfaecb..2761dc40a 100644 --- a/tests/integration/issues/805.phpt +++ b/tests/integration/issues/805.phpt @@ -3,11 +3,17 @@ require 'vendor/autoload.php'; -exceptionMessages(static function (): void { - v::key('email', v::email()->setTemplate('WRONG EMAIL!!!!!!'))->assert(['email' => 'qwe']); -}); +exceptionAll( + 'https://github.com/Respect/Validation/issues/805', + static fn() => v::key('email', v::email()->setTemplate('WRONG EMAIL!!!!!!'))->assert(['email' => 'qwe']) +); ?> --EXPECT-- +https://github.com/Respect/Validation/issues/805 +⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +WRONG EMAIL!!!!!! +- WRONG EMAIL!!!!!! + - WRONG EMAIL!!!!!! [ 'email' => 'WRONG EMAIL!!!!!!', ] From 52e628fc6faba57f62c9287b1851f1555614f412 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Fri, 13 Dec 2024 02:16:16 +0100 Subject: [PATCH 3/4] Rename "siblings" to "subsequent" The name "subsequent" better represents those results. What I would consider a "sibling" would be another child from the same result. --- library/Message/StandardRenderer.php | 4 +-- library/Result.php | 27 +++++++++------------ library/Rules/DateTimeDiff.php | 4 +-- library/Rules/Length.php | 4 +-- library/Rules/Max.php | 2 +- library/Rules/Min.php | 2 +- library/Rules/NullOr.php | 4 +-- library/Rules/UndefOr.php | 4 +-- tests/integration/rules/dateTimeDiff.phpt | 12 ++++----- tests/integration/rules/nullOr.phpt | 12 ++++----- tests/integration/rules/undefOr.phpt | 12 ++++----- tests/library/Builders/ResultBuilder.php | 8 +++--- tests/unit/Message/StandardRendererTest.php | 10 ++++---- 13 files changed, 50 insertions(+), 55 deletions(-) diff --git a/library/Message/StandardRenderer.php b/library/Message/StandardRenderer.php index ffaa6ea9a..2d06b4d3c 100644 --- a/library/Message/StandardRenderer.php +++ b/library/Message/StandardRenderer.php @@ -52,8 +52,8 @@ function (array $matches) use ($parameters, $translator) { $translator->translate($template ?? $this->getTemplateMessage($result)) ); - if (!$result->hasCustomTemplate() && $result->nextSibling !== null) { - $rendered .= ' ' . $this->render($result->nextSibling, $translator); + if (!$result->hasCustomTemplate() && $result->subsequent !== null) { + $rendered .= ' ' . $this->render($result->subsequent, $translator); } return $rendered; diff --git a/library/Result.php b/library/Result.php index 8f175624b..f7f18c69e 100644 --- a/library/Result.php +++ b/library/Result.php @@ -39,7 +39,7 @@ public function __construct( public readonly Mode $mode = Mode::DEFAULT, ?string $name = null, ?string $id = null, - public readonly ?Result $nextSibling = null, + public readonly ?Result $subsequent = null, Result ...$children, ) { $this->name = $rule->getName() ?? $name; @@ -113,16 +113,16 @@ public function withInput(mixed $input): self ); } - public function withNextSibling(Result $nextSibling): self + public function withSubsequent(Result $subsequent): self { - return $this->clone(nextSibling: $nextSibling); + return $this->clone(subsequent: $subsequent); } public function withInvertedValidation(): self { return $this->clone( isValid: !$this->isValid, - nextSibling: $this->nextSibling?->withInvertedValidation(), + subsequent: $this->subsequent?->withInvertedValidation(), children: array_map(static fn (Result $child) => $child->withInvertedValidation(), $this->children), ); } @@ -132,16 +132,11 @@ public function withInvertedMode(): self return $this->clone( isValid: !$this->isValid, mode: $this->mode == Mode::DEFAULT ? Mode::INVERTED : Mode::DEFAULT, - nextSibling: $this->nextSibling?->withInvertedMode(), + subsequent: $this->subsequent?->withInvertedMode(), children: array_map(static fn (Result $child) => $child->withInvertedMode(), $this->children), ); } - public function withMode(Mode $mode): self - { - return $this->clone(mode: $mode); - } - public function hasCustomTemplate(): bool { return preg_match('/__[0-9a-z_]+_/', $this->template) === 0; @@ -162,18 +157,18 @@ public function isAlwaysVisible(): bool return count($childrenAlwaysVisible) !== 1; } - public function isSiblingCompatible(): bool + public function allowsSubsequent(): bool { if ($this->children === [] && !$this->hasCustomTemplate()) { return true; } - $siblingCompatibleChildren = array_filter( + $childrenThatAllowSubsequent = array_filter( $this->children, - static fn (Result $child) => $child->isSiblingCompatible() + static fn (Result $child) => $child->allowsSubsequent() ); - return count($siblingCompatibleChildren) === 1; + return count($childrenThatAllowSubsequent) === 1; } /** @@ -186,7 +181,7 @@ private function clone( ?Mode $mode = null, ?string $name = null, ?string $id = null, - ?Result $nextSibling = null, + ?Result $subsequent = null, ?array $children = null ): self { return new self( @@ -198,7 +193,7 @@ private function clone( $mode ?? $this->mode, $name ?? $this->name, $id ?? $this->id, - $nextSibling ?? $this->nextSibling, + $subsequent ?? $this->subsequent, ...($children ?? $this->children) ); } diff --git a/library/Rules/DateTimeDiff.php b/library/Rules/DateTimeDiff.php index fe0a0833d..07c39aac3 100644 --- a/library/Rules/DateTimeDiff.php +++ b/library/Rules/DateTimeDiff.php @@ -83,7 +83,7 @@ private function enrichResult(string $now, mixed $input, Result $result): Result { $name = $input instanceof DateTimeInterface ? $input->format('c') : $input; - if (!$result->isSiblingCompatible()) { + if (!$result->allowsSubsequent()) { return $result ->withNameIfMissing($name) ->withChildren( @@ -96,7 +96,7 @@ private function enrichResult(string $now, mixed $input, Result $result): Result return (new Result($result->isValid, $input, $this, $parameters, $template, id: $result->id)) ->withPrefixedId('dateTimeDiff') - ->withNextSibling($result->withNameIfMissing($name)); + ->withSubsequent($result->withNameIfMissing($name)); } private function comparisonValue(DateTimeInterface $now, DateTimeInterface $compareTo): int|float diff --git a/library/Rules/Length.php b/library/Rules/Length.php index 280238934..cbe35ff64 100644 --- a/library/Rules/Length.php +++ b/library/Rules/Length.php @@ -29,12 +29,12 @@ public function evaluate(mixed $input): Result if (!$typeResult->isValid) { $result = $this->rule->evaluate($input); - return Result::failed($input, $this)->withNextSibling($result)->withId('length' . ucfirst($result->id)); + return Result::failed($input, $this)->withSubsequent($result)->withId('length' . ucfirst($result->id)); } $result = $this->rule->evaluate($this->extractLength($input))->withInput($input)->withPrefixedId('length'); - return (new Result($result->isValid, $input, $this, id: $result->id))->withNextSibling($result); + return (new Result($result->isValid, $input, $this, id: $result->id))->withSubsequent($result); } /** @param array|PhpCountable|string $input */ diff --git a/library/Rules/Max.php b/library/Rules/Max.php index 97e2d8a7b..7977bf16a 100644 --- a/library/Rules/Max.php +++ b/library/Rules/Max.php @@ -27,6 +27,6 @@ protected function evaluateNonEmptyArray(array $input): Result $result = $this->rule->evaluate(max($input))->withPrefixedId('max'); $template = $this->getName() === null ? self::TEMPLATE_STANDARD : self::TEMPLATE_NAMED; - return (new Result($result->isValid, $input, $this, [], $template, id: $result->id))->withNextSibling($result); + return (new Result($result->isValid, $input, $this, [], $template, id: $result->id))->withSubsequent($result); } } diff --git a/library/Rules/Min.php b/library/Rules/Min.php index d25df5b1b..8e619a6a3 100644 --- a/library/Rules/Min.php +++ b/library/Rules/Min.php @@ -27,6 +27,6 @@ protected function evaluateNonEmptyArray(array $input): Result $result = $this->rule->evaluate(min($input))->withPrefixedId('min'); $template = $this->getName() === null ? self::TEMPLATE_STANDARD : self::TEMPLATE_NAMED; - return (new Result($result->isValid, $input, $this, [], $template, id: $result->id))->withNextSibling($result); + return (new Result($result->isValid, $input, $this, [], $template, id: $result->id))->withSubsequent($result); } } diff --git a/library/Rules/NullOr.php b/library/Rules/NullOr.php index c3db824ac..9faa2f4c7 100644 --- a/library/Rules/NullOr.php +++ b/library/Rules/NullOr.php @@ -37,10 +37,10 @@ public function evaluate(mixed $input): Result private function enrichResult(Result $result): Result { - if ($result->isSiblingCompatible()) { + if ($result->allowsSubsequent()) { return $result ->withPrefixedId('nullOr') - ->withNextSibling(new Result($result->isValid, $result->input, $this)); + ->withSubsequent(new Result($result->isValid, $result->input, $this)); } return $result->withChildren(...array_map(fn(Result $child) => $this->enrichResult($child), $result->children)); diff --git a/library/Rules/UndefOr.php b/library/Rules/UndefOr.php index 8ef959586..8c2bae7d2 100644 --- a/library/Rules/UndefOr.php +++ b/library/Rules/UndefOr.php @@ -40,10 +40,10 @@ public function evaluate(mixed $input): Result private function enrichResult(Result $result): Result { - if ($result->isSiblingCompatible()) { + if ($result->allowsSubsequent()) { return $result ->withPrefixedId('undefOr') - ->withNextSibling(new Result($result->isValid, $result->input, $this)); + ->withSubsequent(new Result($result->isValid, $result->input, $this)); } return $result->withChildren(...array_map(fn(Result $child) => $this->enrichResult($child), $result->children)); diff --git a/tests/integration/rules/dateTimeDiff.phpt b/tests/integration/rules/dateTimeDiff.phpt index e229c615d..dec76fe0e 100644 --- a/tests/integration/rules/dateTimeDiff.phpt +++ b/tests/integration/rules/dateTimeDiff.phpt @@ -26,11 +26,11 @@ run([ v::dateTimeDiff('years', v::equals(2))->setTemplate('Wrapper with custom template'), '1 year ago', ], - 'Not a sibling compatible' => [ + 'Without subsequent result' => [ v::dateTimeDiff('years', v::primeNumber()->between(2, 5)), '1 year ago', ], - 'Not a sibling compatible with templates' => [ + 'Without subsequent result with templates' => [ v::dateTimeDiff('years', v::primeNumber()->between(2, 5)), '1 year ago', [ @@ -155,8 +155,8 @@ Wrapper with custom template 'dateTimeDiffEquals' => 'Wrapper with custom template', ] -Not a sibling compatible -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +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 @@ -167,8 +167,8 @@ 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', ] -Not a sibling compatible with templates -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +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 diff --git a/tests/integration/rules/nullOr.phpt b/tests/integration/rules/nullOr.phpt index fb3e8d601..b2ad3b829 100644 --- a/tests/integration/rules/nullOr.phpt +++ b/tests/integration/rules/nullOr.phpt @@ -18,11 +18,11 @@ run([ null, ['notNullOrAlpha' => 'Next to nifty null notations'], ], - 'Not a sibling compatible rule' => [ + 'Without subsequent result' => [ v::nullOr(v::alpha()->stringType()), 1234, ], - 'Not a sibling compatible rule with templates' => [ + 'Without subsequent result with templates' => [ v::nullOr(v::alpha()->stringType()), 1234, [ @@ -113,8 +113,8 @@ Next to nifty null notations 'notNullOrAlpha' => 'Next to nifty null notations', ] -Not a sibling compatible rule -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +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 @@ -125,8 +125,8 @@ Not a sibling compatible rule 'nullOrStringType' => '1234 must be a string or must be null', ] -Not a sibling compatible rule with templates -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +Without subsequent result with templates +⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ Should be nul or alpha - All of the required rules must pass for 1234 - Should be nul or alpha diff --git a/tests/integration/rules/undefOr.phpt b/tests/integration/rules/undefOr.phpt index 787671667..c6d9581b5 100644 --- a/tests/integration/rules/undefOr.phpt +++ b/tests/integration/rules/undefOr.phpt @@ -18,11 +18,11 @@ run([ '', ['notUndefOrAlpha' => 'Should not be undefined or alpha'], ], - 'Not a sibling compatible rule' => [ + 'Without subsequent result' => [ v::undefOr(v::alpha()->stringType()), 1234, ], - 'Not a sibling compatible rule with templates' => [ + 'Without subsequent result with templates' => [ v::undefOr(v::alpha()->stringType()), 1234, [ @@ -113,8 +113,8 @@ Should not be undefined or alpha 'notUndefOrAlpha' => 'Should not be undefined or alpha', ] -Not a sibling compatible rule -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +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 @@ -125,8 +125,8 @@ Not a sibling compatible rule 'undefOrStringType' => '1234 must be a string or must be undefined', ] -Not a sibling compatible rule with templates -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ +Without subsequent result with templates +⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ Should be nul or alpha - All of the required rules must pass for 1234 - Should be nul or alpha diff --git a/tests/library/Builders/ResultBuilder.php b/tests/library/Builders/ResultBuilder.php index ab8e5d572..40d829028 100644 --- a/tests/library/Builders/ResultBuilder.php +++ b/tests/library/Builders/ResultBuilder.php @@ -33,7 +33,7 @@ final class ResultBuilder private Rule $rule; - private ?Result $nextSibling = null; + private ?Result $subsequent = null; /** @var array */ private array $children = []; @@ -54,7 +54,7 @@ public function build(): Result $this->mode, $this->name, $this->id, - $this->nextSibling, + $this->subsequent, ...$this->children ); } @@ -132,9 +132,9 @@ public function mode(Mode $mode): self return $this; } - public function nextSibling(Result $build): self + public function subsequent(Result $build): self { - $this->nextSibling = $build; + $this->subsequent = $build; return $this; } diff --git a/tests/unit/Message/StandardRendererTest.php b/tests/unit/Message/StandardRendererTest.php index 62b684fb7..984df0ce2 100644 --- a/tests/unit/Message/StandardRendererTest.php +++ b/tests/unit/Message/StandardRendererTest.php @@ -340,14 +340,14 @@ public function itShouldRenderResultWithNonCustomTemplateWhenCannotFindAttachedT } #[Test] - public function itShouldRenderResultWithItsSiblingsWhenItHasNoCustomTemplate(): void + public function itShouldRenderResultWithItsSubsequentsWhenItHasNoCustomTemplate(): void { $renderer = new StandardRenderer(new TestingStringifier()); $result = (new ResultBuilder())->template('__1st__') - ->nextSibling( + ->subsequent( (new ResultBuilder())->template('__2nd__') - ->nextSibling( + ->subsequent( (new ResultBuilder())->template('__3rd__')->build(), ) ->build(), @@ -360,12 +360,12 @@ public function itShouldRenderResultWithItsSiblingsWhenItHasNoCustomTemplate(): } #[Test] - public function itShouldRenderResultWithoutItsSiblingsWhenItHasCustomTemplate(): void + public function itShouldRenderResultWithoutItsSubsequentsWhenItHasCustomTemplate(): void { $template = 'Custom template'; $result = (new ResultBuilder())->template($template) - ->nextSibling((new ResultBuilder())->template('and this is a sibling')->build()) + ->subsequent((new ResultBuilder())->template('and this is a subsequent')->build()) ->build(); $renderer = new StandardRenderer(new TestingStringifier()); From 82cb05b197e8f7fc51c684d339d255acf89673fc Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Thu, 12 Dec 2024 14:04:25 +0100 Subject: [PATCH 4/4] Handle siblings when creating messages The way we display messages could have been better, and it took me a while to realise that to make it better, I would need to handle the siblings of a result before deciding whether we should render it. Another issue was that rules like Key and Property had to create a "dumb" parent just so we would display the messages correctly, and in some cases, that wasn't even enough. This commit introduces quite a few changes to how the library works, making the messages much more straightforward. --- library/Message/Formatter.php | 8 ++- library/Message/StandardFormatter.php | 58 +++++++++++++++++-- library/Result.php | 29 +++++----- library/Rules/Key.php | 8 +-- library/Rules/KeyOptional.php | 8 +-- library/Rules/Property.php | 8 +-- library/Rules/PropertyOptional.php | 8 +-- tests/integration/issues/179.phpt | 5 +- tests/integration/issues/446.phpt | 3 +- tests/integration/issues/799.phpt | 3 +- tests/integration/issues/805.phpt | 1 - tests/integration/rules/keySet.phpt | 10 ++-- tests/library/Builders/ResultBuilder.php | 16 ++--- .../StandardFormatter/FullProvider.php | 51 +++++++++++++++- .../StandardFormatter/ResultCreator.php | 49 +++++++++++++++- 15 files changed, 193 insertions(+), 72 deletions(-) diff --git a/library/Message/Formatter.php b/library/Message/Formatter.php index 33e93678b..1adf1f61d 100644 --- a/library/Message/Formatter.php +++ b/library/Message/Formatter.php @@ -21,7 +21,13 @@ public function main(Result $result, array $templates, Translator $translator): /** * @param array $templates */ - public function full(Result $result, array $templates, Translator $translator, int $depth = 0): string; + public function full( + Result $result, + array $templates, + Translator $translator, + int $depth = 0, + Result ...$siblings + ): string; /** * @param array $templates diff --git a/library/Message/StandardFormatter.php b/library/Message/StandardFormatter.php index 41d7951e9..d3d9a6b7d 100644 --- a/library/Message/StandardFormatter.php +++ b/library/Message/StandardFormatter.php @@ -14,6 +14,7 @@ use function array_filter; use function array_key_exists; +use function array_reduce; use function array_values; use function count; use function current; @@ -51,13 +52,18 @@ public function main(Result $result, array $templates, Translator $translator): /** * @param array $templates */ - public function full(Result $result, array $templates, Translator $translator, int $depth = 0): string - { + public function full( + Result $result, + array $templates, + Translator $translator, + int $depth = 0, + Result ...$siblings + ): string { $selectedTemplates = $this->selectTemplates($result, $templates); $isFinalTemplate = $this->isFinalTemplate($result, $selectedTemplates); $rendered = ''; - if ($result->isAlwaysVisible() || $isFinalTemplate) { + if ($this->isAlwaysVisible($result, ...$siblings) || $isFinalTemplate) { $indentation = str_repeat(' ', $depth * 2); $rendered .= sprintf( '%s- %s' . PHP_EOL, @@ -68,8 +74,15 @@ public function full(Result $result, array $templates, Translator $translator, i } if (!$isFinalTemplate) { - foreach ($this->extractDeduplicatedChildren($result) as $child) { - $rendered .= $this->full($child, $selectedTemplates, $translator, $depth); + $results = $this->extractDeduplicatedChildren($result); + foreach ($results as $child) { + $rendered .= $this->full( + $child, + $selectedTemplates, + $translator, + $depth, + ...array_filter($results, static fn (Result $sibling) => $sibling !== $child) + ); $rendered .= PHP_EOL; } } @@ -117,6 +130,41 @@ public function array(Result $result, array $templates, Translator $translator): return $messages; } + private function isAlwaysVisible(Result $result, Result ...$siblings): bool + { + if ($result->isValid) { + return false; + } + + if ($result->hasCustomTemplate()) { + return true; + } + + $childrenAlwaysVisible = array_filter( + $result->children, + fn (Result $child) => $this->isAlwaysVisible($child, ...array_filter( + $result->children, + static fn (Result $sibling) => $sibling !== $child + )) + ); + if (count($childrenAlwaysVisible) !== 1) { + return true; + } + + if (count($siblings) === 0) { + return false; + } + + return array_reduce( + $siblings, + fn (bool $carry, Result $currentSibling) => $carry || $this->isAlwaysVisible( + $currentSibling, + ...array_filter($siblings, static fn (Result $sibling) => $sibling !== $currentSibling) + ), + true + ); + } + /** @param array $templates */ private function getTemplated(Result $result, array $templates): Result { diff --git a/library/Result.php b/library/Result.php index f7f18c69e..4f5962216 100644 --- a/library/Result.php +++ b/library/Result.php @@ -40,6 +40,7 @@ public function __construct( ?string $name = null, ?string $id = null, public readonly ?Result $subsequent = null, + public readonly bool $unchangeableId = false, Result ...$children, ) { $this->name = $rule->getName() ?? $name; @@ -75,12 +76,21 @@ public function withTemplate(string $template): self public function withId(string $id): self { + if ($this->unchangeableId) { + return $this; + } + return $this->clone(id: $id); } + public function withUnchangeableId(string $id): self + { + return $this->clone(id: $id, unchangeableId: true); + } + public function withPrefixedId(string $prefix): self { - if ($this->id === $this->name) { + if ($this->id === $this->name || $this->unchangeableId) { return $this; } @@ -142,21 +152,6 @@ public function hasCustomTemplate(): bool return preg_match('/__[0-9a-z_]+_/', $this->template) === 0; } - public function isAlwaysVisible(): bool - { - if ($this->isValid) { - return false; - } - - if ($this->hasCustomTemplate()) { - return true; - } - - $childrenAlwaysVisible = array_filter($this->children, static fn (Result $child) => $child->isAlwaysVisible()); - - return count($childrenAlwaysVisible) !== 1; - } - public function allowsSubsequent(): bool { if ($this->children === [] && !$this->hasCustomTemplate()) { @@ -182,6 +177,7 @@ private function clone( ?string $name = null, ?string $id = null, ?Result $subsequent = null, + ?bool $unchangeableId = null, ?array $children = null ): self { return new self( @@ -194,6 +190,7 @@ private function clone( $name ?? $this->name, $id ?? $this->id, $subsequent ?? $this->subsequent, + $unchangeableId ?? $this->unchangeableId, ...($children ?? $this->children) ); } diff --git a/library/Rules/Key.php b/library/Rules/Key.php index 86b4e8099..08eaf0758 100644 --- a/library/Rules/Key.php +++ b/library/Rules/Key.php @@ -37,13 +37,9 @@ public function evaluate(mixed $input): Result return $keyExistsResult; } - $child = $this->rule + return $this->rule ->evaluate($input[$this->key]) - ->withId((string) $this->key); - - return (new Result($child->isValid, $input, $this)) - ->withChildren($child) - ->withId((string) $this->key) + ->withUnchangeableId((string) $this->key) ->withNameIfMissing($this->rule->getName() ?? (string) $this->key); } } diff --git a/library/Rules/KeyOptional.php b/library/Rules/KeyOptional.php index c1a62f7af..ae9a7781c 100644 --- a/library/Rules/KeyOptional.php +++ b/library/Rules/KeyOptional.php @@ -37,13 +37,9 @@ public function evaluate(mixed $input): Result return $keyExistsResult->withInvertedMode(); } - $child = $this->rule + return $this->rule ->evaluate($input[$this->key]) - ->withId((string) $this->key); - - return (new Result($child->isValid, $input, $this)) - ->withChildren($child) - ->withId((string) $this->key) + ->withUnchangeableId((string) $this->key) ->withNameIfMissing($this->rule->getName() ?? (string) $this->key); } } diff --git a/library/Rules/Property.php b/library/Rules/Property.php index 47b82e5bb..90b1180c9 100644 --- a/library/Rules/Property.php +++ b/library/Rules/Property.php @@ -34,13 +34,9 @@ public function evaluate(mixed $input): Result return $propertyExistsResult; } - $childResult = $this->rule + return $this->rule ->evaluate($this->extractPropertyValue($input, $this->propertyName)) - ->withId($this->propertyName); - - return (new Result($childResult->isValid, $input, $this)) - ->withChildren($childResult) - ->withId($this->propertyName) + ->withUnchangeableId($this->propertyName) ->withNameIfMissing($this->rule->getName() ?? $this->propertyName); } } diff --git a/library/Rules/PropertyOptional.php b/library/Rules/PropertyOptional.php index 42e99c8a0..27a9e2c6b 100644 --- a/library/Rules/PropertyOptional.php +++ b/library/Rules/PropertyOptional.php @@ -34,13 +34,9 @@ public function evaluate(mixed $input): Result return $propertyExistsResult->withInvertedMode(); } - $childResult = $this->rule + return $this->rule ->evaluate($this->extractPropertyValue($input, $this->propertyName)) - ->withId($this->propertyName); - - return (new Result($childResult->isValid, $input, $this)) - ->withChildren($childResult) - ->withId($this->propertyName) + ->withUnchangeableId($this->propertyName) ->withNameIfMissing($this->rule->getName() ?? $this->propertyName); } } diff --git a/tests/integration/issues/179.phpt b/tests/integration/issues/179.phpt index 83438cd6a..0a0259593 100644 --- a/tests/integration/issues/179.phpt +++ b/tests/integration/issues/179.phpt @@ -22,8 +22,9 @@ exceptionAll('https://github.com/Respect/Validation/issues/179', static fn() => https://github.com/Respect/Validation/issues/179 ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ host must be a string -- host must be a string -- user must be present +- 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', diff --git a/tests/integration/issues/446.phpt b/tests/integration/issues/446.phpt index cfc2ce7ef..1c2674ac9 100644 --- a/tests/integration/issues/446.phpt +++ b/tests/integration/issues/446.phpt @@ -19,8 +19,7 @@ exceptionAll('https://github.com/Respect/Validation/issues/446', static function https://github.com/Respect/Validation/issues/446 ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ The length of name must be between 2 and 32 -- These rules must pass for `["name": "w", "email": "hello@hello.com"]` - - 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/799.phpt b/tests/integration/issues/799.phpt index ad326e6b4..c8142e45e 100644 --- a/tests/integration/issues/799.phpt +++ b/tests/integration/issues/799.phpt @@ -43,8 +43,7 @@ https://github.com/Respect/Validation/issues/799 https://github.com/Respect/Validation/issues/799 ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ scheme must start with "https" -- These rules must pass for `["scheme": "http", "host": "www.google.com", "path": "/search", "query": "q=respect.github.com"]` - - 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 index 2761dc40a..26b74ef22 100644 --- a/tests/integration/issues/805.phpt +++ b/tests/integration/issues/805.phpt @@ -13,7 +13,6 @@ https://github.com/Respect/Validation/issues/805 ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ WRONG EMAIL!!!!!! - WRONG EMAIL!!!!!! - - WRONG EMAIL!!!!!! [ 'email' => 'WRONG EMAIL!!!!!!', ] diff --git a/tests/integration/rules/keySet.phpt b/tests/integration/rules/keySet.phpt index f782a8d37..7f273eccb 100644 --- a/tests/integration/rules/keySet.phpt +++ b/tests/integration/rules/keySet.phpt @@ -193,8 +193,9 @@ foo must be present multiple rules / two extra keys ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ qux must be an integer -- qux must be an integer -- baz must not be present +- `["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', @@ -216,8 +217,9 @@ bar must be an integer multiple rules / single missing key / single failed validation ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ bar must be an integer -- bar must be an integer -- baz must be present +- `["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', diff --git a/tests/library/Builders/ResultBuilder.php b/tests/library/Builders/ResultBuilder.php index 40d829028..b2f31a5ec 100644 --- a/tests/library/Builders/ResultBuilder.php +++ b/tests/library/Builders/ResultBuilder.php @@ -35,6 +35,8 @@ final class ResultBuilder private ?Result $subsequent = null; + private bool $unchangeableId = false; + /** @var array */ private array $children = []; @@ -55,22 +57,14 @@ public function build(): Result $this->name, $this->id, $this->subsequent, + $this->unchangeableId, ...$this->children ); } - public function isAlwaysVisible(): self + public function isValid(bool $isValid): self { - return $this->withCustomTemplate(); - } - - public function isNotAlwaysVisible(): self - { - $this->template = 'Custom template'; - $this->children = [ - (new self())->withCustomTemplate()->build(), - (new self())->children((new self())->withCustomTemplate()->build())->build(), - ]; + $this->isValid = $isValid; return $this; } diff --git a/tests/unit/Message/StandardFormatter/FullProvider.php b/tests/unit/Message/StandardFormatter/FullProvider.php index c1fdbbddf..9c7a1b5d5 100644 --- a/tests/unit/Message/StandardFormatter/FullProvider.php +++ b/tests/unit/Message/StandardFormatter/FullProvider.php @@ -82,7 +82,8 @@ public static function provideForFull(): array << 1st custom + - 2nd custom + - 2nd > 1st custom - 3rd custom MESSAGE, [ @@ -99,6 +101,7 @@ public static function provideForFull(): array '__root__' => 'Parent custom', '1st' => '1st custom', '2nd' => [ + '__root__' => '2nd custom', '2nd_1st' => '2nd > 1st custom', ], '3rd' => '3rd custom', @@ -110,7 +113,8 @@ public static function provideForFull(): array << [ + self::multiLevelChildrenWithSiblingsThatHaveOnlyOneChild(), + << [ + self::multiLevelChildrenWithSiblingsThatHaveOnlyOneChild(), + << 1st custom + MESSAGE, + [ + 'parent' => [ + '1st' => '1st custom', + '2nd' => [ + '__root__' => '2nd custom', + '2nd_1st' => '2nd > 1st custom', + ], + ], + ], + ], + 'with siblings that dot not have more than one child' => [ + self::multiLevelChildrenWithSiblingsThatHaveMoreThanOneChild(), + <<children( (new ResultBuilder())->id('child')->template('__1st_original__')->build(), (new ResultBuilder())->id('child')->template('__2nd_original__')->build(), - (new ResultBuilder())->id('child')->template('__3rd_original__')->build() + (new ResultBuilder())->id('child')->template('__3rd_original__')->build(), + (new ResultBuilder())->id('child')->template('__4th_original__')->isValid(true)->build(), + ) + ->build(); + } + + private static function multiLevelChildrenWithSiblingsThatHaveOnlyOneChild(): Result + { + return (new ResultBuilder())->id('parent')->template('__parent_original__') + ->children( + (new ResultBuilder())->id('1st') + ->template('__1st_original__') + ->children( + (new ResultBuilder())->id('1st_1st')->template('__1st_1st_original__')->build(), + (new ResultBuilder())->id('1st_2nd')->template('__1st_2nd_original__')->isValid(true)->build() + ) + ->build(), + (new ResultBuilder()) + ->id('2nd') + ->template('__2nd_original__') + ->children( + (new ResultBuilder())->id('2nd_1st')->template('__2nd_1st_original__')->build() + ) + ->build(), + ) + ->build(); + } + + private static function multiLevelChildrenWithSiblingsThatHaveMoreThanOneChild(): Result + { + return (new ResultBuilder())->id('parent')->template('__parent_original__') + ->children( + (new ResultBuilder())->id('1st') + ->template('__1st_original__') + ->children( + (new ResultBuilder())->id('1st_1st')->template('__1st_1st_original__')->build(), + (new ResultBuilder())->id('1st_2nd')->template('__1st_2nd_original__')->build() + ) + ->build(), + (new ResultBuilder()) + ->id('2nd') + ->template('__2nd_original__') + ->children( + (new ResultBuilder())->id('2nd_1st')->template('__2nd_1st_original__')->build(), + (new ResultBuilder())->id('2nd_2nd')->template('__2nd_2nd_original__')->build() + ) + ->build(), + (new ResultBuilder())->id('3nd')->template('__3rd_original__')->build(), ) ->build(); }