refactor(form-field): extract legacy conditional_logic shape normaliser

Three byte-identical copies of `normaliseLegacyGroupShape` lived in
FormFieldService, StoreFormFieldRequest, and UpdateFormFieldRequest.
WS-5d (form_fields.options) would have been the fourth copy. Hoist
the helper to a single public static on FormFieldConditionalLogicService
and have all three call sites delegate.

Implementation:

  - `FormFieldConditionalLogicService::normaliseLegacyShape(array)` —
    pure recursive passthrough. Translates the ARCH §8 JSON group shape
    (`{"all": [...]}` / `{"any": [...]}`) into the service's internal
    `{"operator", "children"}` form. Does NOT validate; malformed shapes
    return as-is and surface downstream as
    `InvalidConditionalLogicSpecException` from `assertSpecsValid`.
  - Group operator catalogue sourced from
    `FormFieldConditionalLogicGroupOperator::values()` instead of an
    `['all', 'any']` literal — single source of truth for future
    operator additions.
  - All three call sites switched to the static method. The two
    FormRequests reach it via the existing `use` import; FormFieldService
    sits in the same namespace.

Behaviour preserved exactly:

  - Existing FormFieldApiTest (cyclic logic rejection),
    FormFieldStrictConditionalLogicRequestTest (strict-validator
    rejection paths), and FormFieldConditionalLogicServiceTest
    (service-level paths) all green without modification.

New unit tests pin the passthrough contract (8 tests):

  - Valid ALL / ANY translations
  - Recursive nested-group translation (depth 2)
  - Internal shape unchanged
  - Condition leaf passthrough
  - Unknown group key (`xor`) returned unchanged for downstream
    `assertSpecsValid` to reject
  - Empty array unchanged
  - Non-array children stripped silently

Tests: 1150 → 1158 green (3110 → 3124 assertions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 00:57:06 +02:00
parent 64f5855fdb
commit 2656818c35
6 changed files with 205 additions and 115 deletions

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\FormBuilder;
use App\Services\FormBuilder\FormFieldConditionalLogicService;
use PHPUnit\Framework\TestCase;
/**
* Unit-level coverage for the `normaliseLegacyShape` static helper
* extracted in WS-5c commit 6 from three byte-identical copies (one in
* FormFieldService, two in the Store/Update FormRequests). The method
* is pure passthrough: malformed shapes are returned as-is, and surface
* downstream as `InvalidConditionalLogicSpecException` from
* `assertSpecsValid`. We pin that contract here.
*/
final class FormFieldConditionalLogicNormaliserTest extends TestCase
{
public function test_translates_all_group_to_internal_form(): void
{
$result = FormFieldConditionalLogicService::normaliseLegacyShape([
'all' => [
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
],
]);
$this->assertSame([
'operator' => 'all',
'children' => [
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
],
], $result);
}
public function test_translates_any_group_to_internal_form(): void
{
$result = FormFieldConditionalLogicService::normaliseLegacyShape([
'any' => [
['field_slug' => 'region', 'operator' => 'equals', 'value' => 'NL'],
['field_slug' => 'region', 'operator' => 'equals', 'value' => 'BE'],
],
]);
$this->assertSame('any', $result['operator']);
$this->assertCount(2, $result['children']);
}
public function test_recursively_normalises_nested_groups(): void
{
$result = FormFieldConditionalLogicService::normaliseLegacyShape([
'all' => [
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
[
'any' => [
['field_slug' => 'region', 'operator' => 'equals', 'value' => 'NL'],
['field_slug' => 'region', 'operator' => 'equals', 'value' => 'BE'],
],
],
],
]);
$this->assertSame('all', $result['operator']);
$this->assertCount(2, $result['children']);
// First child: condition leaf — passthrough.
$this->assertSame(
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
$result['children'][0],
);
// Second child: nested ANY group — translated.
$this->assertSame('any', $result['children'][1]['operator']);
$this->assertCount(2, $result['children'][1]['children']);
}
public function test_passes_through_internal_shape_unchanged(): void
{
$internal = [
'operator' => 'all',
'children' => [
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
],
];
$this->assertSame($internal, FormFieldConditionalLogicService::normaliseLegacyShape($internal));
}
public function test_passes_through_condition_leaf_unchanged(): void
{
// Conditions at root are technically invalid (a tree must start
// with a group); the normaliser returns the leaf as-is and the
// downstream `assertSpecsValid` produces the 422 error.
$leaf = ['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'];
$this->assertSame($leaf, FormFieldConditionalLogicService::normaliseLegacyShape($leaf));
}
public function test_returns_unknown_group_key_as_passthrough_for_downstream_assert(): void
{
// `xor` is not in the FormFieldConditionalLogicGroupOperator enum.
// Normaliser returns the node unchanged; assertSpecsValid will
// raise `InvalidConditionalLogicSpecException` with "Unknown
// group operator ''" because the passthrough lacks `operator`.
$node = [
'xor' => [
['field_slug' => 'a', 'operator' => 'equals', 'value' => 1],
],
];
$this->assertSame($node, FormFieldConditionalLogicService::normaliseLegacyShape($node));
}
public function test_returns_empty_array_unchanged(): void
{
$this->assertSame([], FormFieldConditionalLogicService::normaliseLegacyShape([]));
}
public function test_skips_non_array_children_silently(): void
{
// Defensive: malformed children that are scalars get stripped
// (existing behaviour — `if (is_array($child))` guard). Surfaces
// downstream as a "group requires at least one child" error if
// every child was scalar.
$result = FormFieldConditionalLogicService::normaliseLegacyShape([
'all' => [
'string-not-an-array',
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
42,
],
]);
$this->assertCount(1, $result['children']);
$this->assertSame(
['field_slug' => 'gate', 'operator' => 'equals', 'value' => 'yes'],
$result['children'][0],
);
}
}