mirror of
https://github.com/Respect/Validation.git
synced 2026-03-14 22:35:45 +01:00
Fix input overwrite not propagating to adjacent results
Adjacent results are results that treat the same input. When overwriting the input of a result, we should also overwrite the input of its adjacent result to maintain consistency. Currently, there are no cases where this has caused issues, but this change prevents potential problems. Assisted-by: Claude Code (Opus 4.5)
This commit is contained in:
parent
ec16b3d2df
commit
aafa204307
2 changed files with 338 additions and 3 deletions
|
|
@ -77,7 +77,7 @@ final readonly class Result
|
|||
if ($this->allowsAdjacent()) {
|
||||
return clone ($result, [
|
||||
'id' => $this->id->withPrefix($prefix),
|
||||
'adjacent' => clone($this, ['input' => $result->input]),
|
||||
'adjacent' => $this->withInput($result->input),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +145,10 @@ final readonly class Result
|
|||
return clone ($this, [
|
||||
'name' => null,
|
||||
'adjacent' => $this->adjacent?->withoutName(),
|
||||
'children' => $this->mapChildren(fn(Result $r) => $r->name === $this->name ? $r->withoutName() : $r),
|
||||
'children' => $this->mapChildrenIf(
|
||||
fn(Result $child) => $child->name === $this->name,
|
||||
static fn(Result $child) => $child->withoutName(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +167,10 @@ final readonly class Result
|
|||
'name' => $name,
|
||||
'hasPrecedentName' => $this->path === null,
|
||||
'adjacent' => $this->adjacent?->withName($name),
|
||||
'children' => $this->mapChildren(static fn(Result $r) => $r->name === null ? $r->withName($name) : $r),
|
||||
'children' => $this->mapChildrenIf(
|
||||
static fn(Result $child) => $child->name === null,
|
||||
static fn(Result $child) => $child->withName($name),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +191,18 @@ final readonly class Result
|
|||
return clone($this, ['adjacent' => $adjacent]);
|
||||
}
|
||||
|
||||
public function withInput(mixed $input): self
|
||||
{
|
||||
return clone($this, [
|
||||
'input' => $input,
|
||||
'adjacent' => $this->adjacent?->withInput($input),
|
||||
'children' => $this->mapChildrenIf(
|
||||
fn(Result $child) => $child->input === $this->input && $child->path === $this->path,
|
||||
static fn(Result $child) => $child->withInput($input),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function withToggledValidation(): self
|
||||
{
|
||||
return clone($this, [
|
||||
|
|
@ -227,4 +245,14 @@ final readonly class Result
|
|||
{
|
||||
return $this->children === [] ? [] : array_map($callback, $this->children);
|
||||
}
|
||||
|
||||
/** @return array<Result> */
|
||||
private function mapChildrenIf(callable $condition, callable $callback): array
|
||||
{
|
||||
if ($this->children === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(static fn(self $child) => $condition($child) ? $callback($child) : $child, $this->children);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
307
tests/unit/ResultTest.php
Normal file
307
tests/unit/ResultTest.php
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\Builders\ResultBuilder;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('core')]
|
||||
#[CoversClass(Result::class)]
|
||||
final class ResultTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itShouldUpdateInputWhenWithInputIsCalled(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateAdjacentInputWhenWithInputIsCalled(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
|
||||
$adjacent = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->adjacent($adjacent)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($newInput, $updatedResult->adjacent?->input);
|
||||
self::assertSame($originalInput, $result->adjacent?->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateChildrenInputWhenWithInputIsCalledAndChildHasSameInputAndPath(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
$path = new Path('parent');
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($newInput, $updatedResult->children[0]->input);
|
||||
self::assertSame($originalInput, $result->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateChildrenInputWhenWithInputIsCalledAndBothHaveNullPath(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($newInput, $updatedResult->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldNotUpdateChildrenInputWhenWithInputIsCalledAndChildHasDifferentInput(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
$childInput = 'different';
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($childInput)
|
||||
->path(new Path('parent'))
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('parent'))
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($childInput, $updatedResult->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateOnlyMatchingChildrenInputWhenWithInputIsCalled(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
$differentInput = 'different';
|
||||
$path = new Path('parent');
|
||||
|
||||
$matchingChild = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->build();
|
||||
|
||||
$differentChild = (new ResultBuilder())
|
||||
->input($differentInput)
|
||||
->path($path)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->children($matchingChild, $differentChild)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($newInput, $updatedResult->children[0]->input);
|
||||
self::assertSame($differentInput, $updatedResult->children[1]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldNotUpdateChildrenInputWhenWithInputIsCalledAndChildHasDifferentPath(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('child'))
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('parent'))
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($originalInput, $updatedResult->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateInputAdjacentAndChildrenWithSameInputWhenWithInputIsCalled(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
$path = new Path('parent');
|
||||
|
||||
$adjacent = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->build();
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->adjacent($adjacent)
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertNotNull($updatedResult->adjacent);
|
||||
self::assertSame($newInput, $updatedResult->adjacent->input);
|
||||
self::assertSame($newInput, $updatedResult->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateNestedChildrenInputWhenWithInputIsCalled(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
$path = new Path('parent');
|
||||
|
||||
$grandchild = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->build();
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->children($grandchild)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path($path)
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($newInput, $updatedResult->children[0]->input);
|
||||
self::assertSame($newInput, $updatedResult->children[0]->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldNotUpdateNestedChildrenWhenWithInputIsCalledAndGrandchildHasDifferentPath(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
$grandchildInput = 'grandchild_input';
|
||||
|
||||
$grandchild = (new ResultBuilder())
|
||||
->input($grandchildInput)
|
||||
->path(new Path('grandchild'))
|
||||
->build();
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('child'))
|
||||
->children($grandchild)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('parent'))
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($originalInput, $updatedResult->children[0]->input);
|
||||
self::assertSame($grandchildInput, $updatedResult->children[0]->children[0]->input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateNestedChildrenWhenWithInputIsCalledAndGrandchildHasSameInputAndPath(): void
|
||||
{
|
||||
$originalInput = 'original';
|
||||
$newInput = 'updated';
|
||||
|
||||
$grandchild = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('grandchild'))
|
||||
->build();
|
||||
|
||||
$child = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('child'))
|
||||
->children($grandchild)
|
||||
->build();
|
||||
|
||||
$result = (new ResultBuilder())
|
||||
->input($originalInput)
|
||||
->path(new Path('result'))
|
||||
->children($child)
|
||||
->build();
|
||||
|
||||
$updatedResult = $result->withInput($newInput);
|
||||
|
||||
self::assertSame($newInput, $updatedResult->input);
|
||||
self::assertSame($originalInput, $updatedResult->children[0]->input);
|
||||
self::assertSame($originalInput, $updatedResult->children[0]->children[0]->input);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue