Files
crewli/api/tests/Feature/FormBuilder/FormFieldOptionsAccessTest.php
bert.hausmans e7c9482474 refactor(form-field): drop form_fields.options + form_field_library.options
Final WS-5d cleanup. The JSON columns that have been unread since
commit 3 are now physically dropped on both source tables. Their
canonical rich-shape lives in form_field_options, accessed
exclusively through the morphMany relation.

Defensive sweep: any lingering translations.{locale}.options key in
either source table's translations bag is stripped. Commit 2's
backfill should already have done so exhaustively; this is
belt-and-braces.

Rollback re-creates the columns as nullable JSON but leaves them
empty. Pair with commit 2's rollback to restore the pre-WS-5d data
shape on every owner row.

The commit-3 getOptionsAttribute accessor-bridge on FormField +
FormFieldLibrary is removed — Eloquent's getAttribute() resolution
now naturally falls through to the morphMany relation since there's
no underlying column to shadow it. New regression test
FormFieldOptionsAccessTest asserts $field->options resolves to an
Eloquent Collection of FormFieldOption instances and lazy-loads in
exactly 2 queries (1 parent + 1 lazy-load options) on a fresh fetch
without with() preload. Same trio for FormFieldLibrary.

Migration step-count tests in WS-5a/b/c bumped by 1 to account for
the new drop_form_field_options_json_columns migration on the
rollback stack.

Documentation:
  - SCHEMA.md v2.6: form_field_options table documented; options row
    removed from form_fields and form_field_library; morphMany
    relations updated; cross-references to ARCH-FORM-BUILDER §17.6
    and addendum §Q3 WS-5d Uitvoering added on both source-table
    docblocks.
  - ARCH-FORM-BUILDER.md v1.8: new §17.6 "Field options (relational)"
    mirrors the §17.4 / §17.5 relational-sibling structure with
    sub-sections 17.6.1 rationale, 17.6.2 table + catalogue, 17.6.3
    service / scope / cascade / activity log, 17.6.4 snapshot
    embedding, 17.6.5 external API contract. Existing Webhooks
    section renumbered from §17.6 to §17.7.
  - ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md: "Uitvoering — WS-5d
    (2026-04-27)" section added. Eight paragraphs covering the
    snapshot atomic rewrite, strict-fail backfill dispatch, dual
    activity-log emit, four-sibling base-class extraction warrant,
    commit 0 dead-code precondition, the temporary getOptionsAttribute
    accessor-bridge pattern (with reusability note for future
    JSON→relational refactors), the dev-seeder vergoedingstype RADIO
    normalisation (drift correction explicitly distinguished from the
    parallel apps/app RegistrationFieldTemplate description domain),
    and the WS-5 family completion note.
  - BACKLOG.md: FORM-BUILDER-LIBRARY-AUDIT-LOG entry extended to four
    services (adds library.options_replaced); new
    FORM-BUILDER-MORPH-SCOPE-BASE-CLASS entry added as the WS-5d
    follow-up now that all four concrete morph-scope siblings exist.

Tests: 1193 → 1208 green (+15 across commits 3+4+5; this commit alone:
+2 from the regression test).

This completes the WS-5 family.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 03:00:20 +02:00

111 lines
3.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Tests\Feature\FormBuilder;
use App\Enums\FormBuilder\FormFieldType;
use App\Models\FormBuilder\FormField;
use App\Models\FormBuilder\FormFieldLibrary;
use App\Models\FormBuilder\FormFieldOption;
use App\Models\FormBuilder\FormSchema;
use App\Models\Organisation;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
/**
* Regression test for the post-WS-5d-commit-5 access pattern. With the
* form_fields.options / form_field_library.options JSON columns dropped
* (and the temporary getOptionsAttribute accessor-bridge removed),
* Eloquent's getAttribute('options') falls through to the morphMany
* relation naturally. `$model->options` (no parens) must:
*
* - return an Eloquent Collection
* - whose entries are FormFieldOption instances
* - lazily lazy-load on first access (1 query for the parent fetch
* plus 1 query for the lazy-load — no surprise extra reads)
*
* Tested for both polymorphic owners — FormField and FormFieldLibrary.
*/
final class FormFieldOptionsAccessTest extends TestCase
{
use RefreshDatabase;
public function test_form_field_options_resolves_to_morph_many_collection_with_lazy_load(): void
{
$org = Organisation::factory()->create();
$schema = FormSchema::factory()->create(['organisation_id' => $org->id]);
FormField::factory()
->withOptions(['XS', 'S', 'M'])
->create([
'form_schema_id' => $schema->id,
'field_type' => FormFieldType::SELECT->value,
'slug' => 'shirtmaat',
]);
DB::flushQueryLog();
DB::enableQueryLog();
// Fresh fetch — no with('options') eager-load. The lazy-load
// happens on first $field->options access.
$field = FormField::query()->where('slug', 'shirtmaat')->first();
$options = $field->options;
DB::disableQueryLog();
$queries = DB::getQueryLog();
$this->assertInstanceOf(Collection::class, $options);
$this->assertNotEmpty($options);
$this->assertInstanceOf(FormFieldOption::class, $options->first());
$this->assertSame(['XS', 'S', 'M'], $options->pluck('value')->all());
// Exactly two queries: 1× FormField fetch + 1× lazy-load options.
$this->assertCount(
2,
$queries,
sprintf(
'Expected exactly 2 queries (parent fetch + lazy-load options); got %d. Queries: %s',
count($queries),
json_encode(array_column($queries, 'query')),
),
);
}
public function test_form_field_library_options_resolves_to_morph_many_collection_with_lazy_load(): void
{
$org = Organisation::factory()->create();
FormFieldLibrary::factory()
->withOptions(['a', 'b'])
->create([
'organisation_id' => $org->id,
'slug' => 'lib-select',
]);
DB::flushQueryLog();
DB::enableQueryLog();
$library = FormFieldLibrary::query()->where('slug', 'lib-select')->first();
$options = $library->options;
DB::disableQueryLog();
$queries = DB::getQueryLog();
$this->assertInstanceOf(Collection::class, $options);
$this->assertNotEmpty($options);
$this->assertInstanceOf(FormFieldOption::class, $options->first());
$this->assertSame(['a', 'b'], $options->pluck('value')->all());
$this->assertCount(
2,
$queries,
sprintf(
'Expected exactly 2 queries (parent fetch + lazy-load options); got %d. Queries: %s',
count($queries),
json_encode(array_column($queries, 'query')),
),
);
}
}