* SPDX-FileContributor: Dominick Johnson * SPDX-FileContributor: Henrique Moody */ declare(strict_types=1); test('Non-iterable', catchAll( fn() => v::each(v::intType())->assert(null), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`null` must be iterable') ->and($fullMessage)->toBe('- `null` must be iterable') ->and($messages)->toBe(['each' => '`null` must be iterable']), )); test('Default', catchAll( fn() => v::each(v::intType())->assert(['a', 'b', 'c']), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` must be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in `["a", "b", "c"]` must be valid - `.0` must be an integer - `.1` must be an integer - `.2` must be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in `["a", "b", "c"]` must be valid', 0 => '`.0` must be an integer', 1 => '`.1` must be an integer', 2 => '`.2` must be an integer', ]), )); test('Inverted', catchAll( fn() => v::not(v::each(v::intType()))->assert([1, 2, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` must not be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in `[1, 2, 3]` must be invalid - `.0` must not be an integer - `.1` must not be an integer - `.2` must not be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in `[1, 2, 3]` must be invalid', 0 => '`.0` must not be an integer', 1 => '`.1` must not be an integer', 2 => '`.2` must not be an integer', ]), )); test('With name, non-iterable', catchAll( fn() => v::each(v::named('Wrapped', v::intType()))->assert(null), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`null` must be iterable') ->and($fullMessage)->toBe('- `null` must be iterable') ->and($messages)->toBe(['each' => '`null` must be iterable']), )); test('With name, default', catchAll( fn() => v::named('Outer', v::each(v::named('Inner', v::intType())))->assert(['a', 'b', 'c']), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` (<- Inner) must be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in Outer must be valid - `.0` (<- Inner) must be an integer - `.1` (<- Inner) must be an integer - `.2` (<- Inner) must be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in Outer must be valid', 0 => '`.0` (<- Inner) must be an integer', 1 => '`.1` (<- Inner) must be an integer', 2 => '`.2` (<- Inner) must be an integer', ]), )); test('With name, inverted', catchAll( fn() => v::named('Not', v::not(v::named('Outer', v::each(v::named('Inner', v::intType())))))->assert([1, 2, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` (<- Inner) must not be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in Outer must be invalid - `.0` (<- Inner) must not be an integer - `.1` (<- Inner) must not be an integer - `.2` (<- Inner) must not be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in Outer must be invalid', 0 => '`.0` (<- Inner) must not be an integer', 1 => '`.1` (<- Inner) must not be an integer', 2 => '`.2` (<- Inner) must not be an integer', ]), )); test('With wrapper name, default', catchAll( fn() => v::named('Wrapper', v::each(v::intType()))->assert(['a', 'b', 'c']), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` (<- Wrapper) must be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in Wrapper must be valid - `.0` must be an integer - `.1` must be an integer - `.2` must be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in Wrapper must be valid', 0 => '`.0` must be an integer', 1 => '`.1` must be an integer', 2 => '`.2` must be an integer', ]), )); test('With wrapper name, inverted', catchAll( fn() => v::named('Not', v::not(v::named('Wrapper', v::each(v::intType()))))->assert([1, 2, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` (<- Wrapper) must not be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in Wrapper must be invalid - `.0` must not be an integer - `.1` must not be an integer - `.2` must not be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in Wrapper must be invalid', 0 => '`.0` must not be an integer', 1 => '`.1` must not be an integer', 2 => '`.2` must not be an integer', ]), )); test('With Not name, inverted', catchAll( fn() => v::named('Not', v::not(v::each(v::intType())))->assert([1, 2, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` (<- Not) must not be an integer') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in Not must be invalid - `.0` must not be an integer - `.1` must not be an integer - `.2` must not be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in Not must be invalid', 0 => '`.0` must not be an integer', 1 => '`.1` must not be an integer', 2 => '`.2` must not be an integer', ]), )); test('With template, non-iterable', catchAll( fn() => v::templated('You should have passed an iterable', v::each(v::intType()))->assert(null), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('You should have passed an iterable') ->and($fullMessage)->toBe('- You should have passed an iterable') ->and($messages)->toBe(['each' => 'You should have passed an iterable']), )); test('With template, default', catchAll( fn() => v::templated('All items should have been integers', v::each(v::intType())) ->assert(['a', 'b', 'c']), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('All items should have been integers') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - All items should have been integers - `.0` must be an integer - `.1` must be an integer - `.2` must be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'All items should have been integers', 0 => '`.0` must be an integer', 1 => '`.1` must be an integer', 2 => '`.2` must be an integer', ]), )); test('with template, inverted', catchAll( fn() => v::templated('All items should not have been integers', v::not(v::each(v::intType()))) ->assert([1, 2, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('All items should not have been integers') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - All items should not have been integers - `.0` must not be an integer - `.1` must not be an integer - `.2` must not be an integer FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'All items should not have been integers', 0 => '`.0` must not be an integer', 1 => '`.1` must not be an integer', 2 => '`.2` must not be an integer', ]), )); test('With array template, default', catchAll( fn() => v::each(v::intType()) ->assert(['a', 'b', 'c'], [ '__root__' => 'Here a sequence of items that did not pass the validation', 0 => 'First item should have been an integer', 1 => 'Second item should have been an integer', 2 => 'Third item should have been an integer', ]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('First item should have been an integer') ->and($fullMessage)->toBe(<<<'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) ->and($messages)->toBe([ '__root__' => 'Here a sequence of items that did not pass the validation', 0 => 'First item should have been an integer', 1 => 'Second item should have been an integer', 2 => 'Third item should have been an integer', ]), )); test('With array template and name, default', catchAll( fn() => v::named('Wrapper', v::each(v::named('Wrapped', v::intType()))) ->assert(['a', 'b', 'c'], [ '__root__' => 'Here a sequence of items that did not pass the validation', 0 => 'First item should have been an integer', 1 => 'Second item should have been an integer', 2 => 'Third item should have been an integer', ]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('First item should have been an integer') ->and($fullMessage)->toBe(<<<'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) ->and($messages)->toBe([ '__root__' => 'Here a sequence of items that did not pass the validation', 0 => 'First item should have been an integer', 1 => 'Second item should have been an integer', 2 => 'Third item should have been an integer', ]), )); test('Chained wrapped rule', catchAll( fn() => v::each(v::between(5, 7)->odd())->assert([2, 4]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` must be between 5 and 7') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in `[2, 4]` must be valid - `.0` must pass all the rules - `.0` must be between 5 and 7 - `.0` must be an odd number - `.1` must pass all the rules - `.1` must be between 5 and 7 - `.1` must be an odd number FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in `[2, 4]` must be valid', 0 => [ '__root__' => '`.0` must pass all the rules', 0 => '`.0` must be between 5 and 7', 1 => '`.0` must be an odd number', ], 1 => [ '__root__' => '`.1` must pass all the rules', 0 => '`.1` must be between 5 and 7', 1 => '`.1` must be an odd number', ], ]), )); test('Multiple nested rules', catchAll( fn() => v::each(v::arrayType()->key('my_int', v::intType()->odd())->length(v::equals(1)))->assert([['not_int' => 'wrong'], ['my_int' => 2], 'not an array']), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0.my_int` must be present') ->and($fullMessage)->toBe(<<<'FULL_MESSAGE' - Each item in `[["not_int": "wrong"], ["my_int": 2], "not an array"]` must be valid - `.0` must pass the rules - `.0.my_int` must be present - `.1` must pass the rules - `.1.my_int` must be an odd number - `.2` must pass all the rules - `.2` must be an array - `.2.my_int` must be present - The length of `.2` must be equal to 1 FULL_MESSAGE) ->and($messages)->toBe([ '__root__' => 'Each item in `[["not_int": "wrong"], ["my_int": 2], "not an array"]` must be valid', 0 => '`.0.my_int` must be present', 1 => '`.1.my_int` must be an odd number', 2 => [ '__root__' => '`.2` must pass all the rules', 'arrayType' => '`.2` must be an array', 'my_int' => '`.2.my_int` must be present', 'lengthEquals' => 'The length of `.2` must be equal to 1', ], ]), )); test('short-circuit: first item fails', catchAll( fn() => v::shortCircuit(v::each(v::intType()))->assert(['a', 2, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.0` must be an integer') ->and($fullMessage)->toBe('- `.0` must be an integer') ->and($messages)->toBe([0 => '`.0` must be an integer']), )); test('short-circuit: second item fails', catchAll( fn() => v::shortCircuit(v::each(v::intType()))->assert([1, 2.5, 3]), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`.1` must be an integer') ->and($fullMessage)->toBe('- `.1` must be an integer') ->and($messages)->toBe([1 => '`.1` must be an integer']), )); test('short-circuit: all items pass', function (): void { $validator = v::shortCircuit(v::each(v::intType())); expect($validator->isValid([1, 2, 3]))->toBeTrue(); }); test('short-circuit: empty array', function (): void { $validator = v::shortCircuit(v::each(v::intType())); expect($validator->isValid([]))->toBeTrue(); }); test('short-circuit: non-iterable input', catchAll( fn() => v::shortCircuit(v::each(v::intType()))->assert(null), fn(string $message, string $fullMessage, array $messages) => expect() ->and($message)->toBe('`null` must be iterable') ->and($fullMessage)->toBe('- `null` must be iterable') ->and($messages)->toBe(['each' => '`null` must be iterable']), ));