mirror of
https://github.com/Respect/Validation.git
synced 2026-03-15 14:55:44 +01:00
The `findByPath()` method was failing to return results when using nested dot-notation paths such as `user.email` or `items.1`. However, it’s returning `null` instead of the expected result in some cases. The root cause was a mismatch between how paths are stored vs searched: - Storage: Validators like Key and Each create results where the path is stored as a linked list. For `user.email`, the "email" result has `path="email"` with `parent="user"`. - Search (old): The method expected a tree structure where it would find a child with `path="user"`, then search that child for `path="email"`. But no child had `path="user"` - only "email" (with "user" as its parent). The fix computes each result's full path by walking up the parent chain and compares it against the search path. Also converts numeric strings to integers when parsing paths (e.g., `items.1` → `['items', 1]`) since array indices are stored as integers. While working on this fix, I also realised that to expose the result's status, it’s best to use `hasFailed()` instead of `isValid()` in `ResultQuery`, since users will mostly use results when validation failed, not when it passed. Assisted-by: Claude Code (Opus 4.5)
168 lines
4.8 KiB
PHP
168 lines
4.8 KiB
PHP
<?php
|
|
|
|
/*
|
|
* SPDX-License-Identifier: MIT
|
|
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
|
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
|
* SPDX-FileContributor: Andy Wendt <andy@awendt.com>
|
|
* SPDX-FileContributor: Gabriel Caruso <carusogabriel34@gmail.com>
|
|
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
|
|
* SPDX-FileContributor: Nick Lombard <github@jigsoft.co.za>
|
|
* SPDX-FileContributor: Kir Kolyshkin <kolyshkin@gmail.com>
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Respect\Validation;
|
|
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Respect\Validation\Exceptions\ComponentException;
|
|
use Respect\Validation\Exceptions\ValidationException;
|
|
use Respect\Validation\Test\TestCase;
|
|
use Respect\Validation\Test\Validators\Stub;
|
|
|
|
use function uniqid;
|
|
|
|
#[CoversClass(ValidatorBuilder::class)]
|
|
final class ValidatorTest extends TestCase
|
|
{
|
|
#[Test]
|
|
public function invalidRuleClassShouldThrowComponentException(): void
|
|
{
|
|
$this->expectException(ComponentException::class);
|
|
|
|
// @phpstan-ignore-next-line
|
|
ValidatorBuilder::iDoNotExistSoIShouldThrowException();
|
|
}
|
|
|
|
#[Test]
|
|
public function shouldReturnValidatorInstanceWhenTheNotRuleIsCalledWithArguments(): void
|
|
{
|
|
$validator = ValidatorBuilder::init();
|
|
|
|
// @phpstan-ignore-next-line
|
|
self::assertNotSame($validator, $validator->not($validator->falsy()));
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldProxyResultWithTheIsValidMethod(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
|
|
self::assertFalse($validator->isValid('whatever'));
|
|
}
|
|
|
|
#[Test]
|
|
#[DoesNotPerformAssertions]
|
|
public function itShouldAssertAndNotThrowAnExceptionWhenValidatorPasses(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::pass(1));
|
|
$validator->assert('whatever');
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldAssertAndThrowAnExceptionWhenValidatorFails(): void
|
|
{
|
|
$this->expectException(ValidationException::class);
|
|
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
$validator->assert('whatever');
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldAssertUsingTheGivingStringTemplate(): void
|
|
{
|
|
$template = 'This is my new template';
|
|
|
|
$this->expectExceptionMessage($template);
|
|
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
$validator->assert('whatever', $template);
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldValidateAndReturnValidResultQueryWhenValidationPasses(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::pass(1));
|
|
|
|
$resultQuery = $validator->validate('whatever');
|
|
|
|
self::assertFalse($resultQuery->hasFailed());
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldValidateAndReturnInvalidResultQueryWhenValidationFails(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
|
|
$resultQuery = $validator->validate('whatever');
|
|
|
|
self::assertTrue($resultQuery->hasFailed());
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldValidateUsingStringTemplateWhenProvided(): void
|
|
{
|
|
$template = uniqid();
|
|
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
|
|
$resultQuery = $validator->validate('whatever', $template);
|
|
|
|
self::assertSame($template, $resultQuery->getMessage());
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldValidateUsingArrayTemplatesWhenProvided(): void
|
|
{
|
|
$template = uniqid();
|
|
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
|
|
$resultQuery = $validator->validate('whatever', ['stub' => $template]);
|
|
|
|
self::assertSame($template, $resultQuery->getMessage());
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldEvaluateAndThrowExceptionWhenNoValidatorsAreAdded(): void
|
|
{
|
|
$this->expectException(ComponentException::class);
|
|
$this->expectExceptionMessage('No validators have been added.');
|
|
|
|
$validator = ValidatorBuilder::init();
|
|
$validator->evaluate('whatever');
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldEvaluateAndReturnResultWhenOneRuleIsAdded(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::pass(1));
|
|
|
|
$result = $validator->evaluate('whatever');
|
|
|
|
self::assertTrue($result->hasPassed);
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldEvaluateAndReturnResultWhenMultipleValidatorsAreAdded(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::pass(1), Stub::fail(2));
|
|
|
|
$result = $validator->evaluate('whatever');
|
|
|
|
self::assertFalse($result->hasPassed);
|
|
}
|
|
|
|
#[Test]
|
|
public function itShouldEvaluateAndReturnResultWhenSingleFailingRuleIsAdded(): void
|
|
{
|
|
$validator = ValidatorBuilder::init(Stub::fail(1));
|
|
|
|
$result = $validator->evaluate('whatever');
|
|
|
|
self::assertFalse($result->hasPassed);
|
|
}
|
|
}
|