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>
This commit is contained in:
@@ -33,14 +33,15 @@ final class FormFieldBindingMigrationTest extends TestCase
|
||||
|
||||
public function test_forward_migrations_backfill_rows_from_both_json_sources(): void
|
||||
{
|
||||
// Roll back to pre-WS-5a state: 2 WS-5d migrations (backfill-options,
|
||||
// create-options) + 4 WS-5c migrations (drop-conditional-logic-col,
|
||||
// backfill-conditional-logic, create-conditional-logic-conditions,
|
||||
// Roll back to pre-WS-5a state: 3 WS-5d migrations (drop-options-cols,
|
||||
// backfill-options, create-options) + 4 WS-5c migrations
|
||||
// (drop-conditional-logic-col, backfill-conditional-logic,
|
||||
// create-conditional-logic-conditions,
|
||||
// create-conditional-logic-groups) + 5 WS-5b migrations
|
||||
// (drop-validation-cols, configs-backfill, create-configs,
|
||||
// validation-rules-backfill, create-validation-rules) +
|
||||
// 2 WS-5a migrations (drop-binding-cols, create-bindings) = 13.
|
||||
$this->artisan('migrate:rollback', ['--step' => 13])->assertSuccessful();
|
||||
// 2 WS-5a migrations (drop-binding-cols, create-bindings) = 14.
|
||||
$this->artisan('migrate:rollback', ['--step' => 14])->assertSuccessful();
|
||||
$this->assertFalse(Schema::hasTable('form_field_bindings'));
|
||||
$this->assertTrue(Schema::hasColumn('form_fields', 'binding'));
|
||||
$this->assertTrue(Schema::hasColumn('form_field_library', 'default_binding'));
|
||||
@@ -101,8 +102,8 @@ final class FormFieldBindingMigrationTest extends TestCase
|
||||
|
||||
public function test_rollback_reconstructs_json_and_drops_table(): void
|
||||
{
|
||||
// Walk back the full WS-5d + WS-5c + WS-5b + WS-5a stack (13 migrations).
|
||||
$this->artisan('migrate:rollback', ['--step' => 13])->assertSuccessful();
|
||||
// Walk back the full WS-5d + WS-5c + WS-5b + WS-5a stack (14 migrations).
|
||||
$this->artisan('migrate:rollback', ['--step' => 14])->assertSuccessful();
|
||||
[$fieldAId, , ] = $this->seedFieldsWithBindingJson();
|
||||
[$libAId, ] = $this->seedLibraryWithBindingJson();
|
||||
|
||||
@@ -112,12 +113,12 @@ final class FormFieldBindingMigrationTest extends TestCase
|
||||
$this->assertFalse(Schema::hasColumn('form_fields', 'binding'));
|
||||
$this->assertSame(5, DB::table('form_field_bindings')->count());
|
||||
|
||||
// Step back over WS-5d (2 migrations) + WS-5c (4 migrations) +
|
||||
// Step back over WS-5d (3 migrations) + WS-5c (4 migrations) +
|
||||
// WS-5b (5 migrations) in one go → restores the pre-WS-5b state
|
||||
// (conditional-logic, validation-rules, configs and options tables
|
||||
// gone, validation_rules JSON columns reappear on source tables;
|
||||
// binding contract intact).
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
// gone, validation_rules + options JSON columns reappear on source
|
||||
// tables; binding contract intact).
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
$this->assertFalse(Schema::hasTable('form_field_options'));
|
||||
$this->assertFalse(Schema::hasTable('form_field_conditional_logic_groups'));
|
||||
$this->assertFalse(Schema::hasTable('form_field_conditional_logic_conditions'));
|
||||
|
||||
@@ -31,11 +31,11 @@ final class ConditionalLogicBackfillTest extends TestCase
|
||||
|
||||
public function test_forward_backfill_builds_nested_tree_from_legacy_json(): void
|
||||
{
|
||||
// Roll back the WS-5d backfill-options + create-options + WS-5c
|
||||
// drop-cl-col + WS-5c backfill-cl migrations to land in the
|
||||
// conditional-logic JSON-era state with no relational
|
||||
// form_field_options table yet.
|
||||
$this->artisan('migrate:rollback', ['--step' => 4])->assertSuccessful();
|
||||
// Roll back the WS-5d drop-options-cols + backfill-options +
|
||||
// create-options + WS-5c drop-cl-col + WS-5c backfill-cl
|
||||
// migrations to land in the conditional-logic JSON-era state with
|
||||
// no relational form_field_options table yet.
|
||||
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
|
||||
$this->assertTrue(Schema::hasColumn('form_fields', 'conditional_logic'));
|
||||
|
||||
$fieldId = $this->seedFieldWithJson([
|
||||
@@ -156,7 +156,7 @@ final class ConditionalLogicBackfillTest extends TestCase
|
||||
]);
|
||||
|
||||
// Roll back only the backfill migration — writes the JSON back.
|
||||
$this->artisan('migrate:rollback', ['--step' => 4])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
|
||||
|
||||
$reconstructed = DB::table('form_fields')
|
||||
->where('id', $fieldId)
|
||||
@@ -183,7 +183,7 @@ final class ConditionalLogicBackfillTest extends TestCase
|
||||
|
||||
public function test_unknown_top_level_key_fails_migration(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 4])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithJson([
|
||||
'hide_when' => ['all' => [['field_slug' => 'x', 'operator' => 'equals', 'value' => 1]]],
|
||||
@@ -196,7 +196,7 @@ final class ConditionalLogicBackfillTest extends TestCase
|
||||
|
||||
public function test_unknown_comparison_operator_fails_migration(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 4])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 5])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithJson([
|
||||
'show_when' => ['all' => [['field_slug' => 'x', 'operator' => 'matches_regex', 'value' => 'y']]],
|
||||
|
||||
110
api/tests/Feature/FormBuilder/FormFieldOptionsAccessTest.php
Normal file
110
api/tests/Feature/FormBuilder/FormFieldOptionsAccessTest.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?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')),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
// Roll back only the backfill migration (latest WS-5d step).
|
||||
// Leaves the form_field_options table in place, JSON columns
|
||||
// present on the source tables, and snapshots in pre-WS-5d shape.
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->assertTrue(Schema::hasTable('form_field_options'));
|
||||
$this->assertTrue(Schema::hasColumn('form_fields', 'options'));
|
||||
|
||||
@@ -113,7 +113,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_rollback_reconstructs_json_columns_and_snapshots(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
[$selectId, $multiId, $libraryId] = $this->seedFieldsAndLibraryWithJson();
|
||||
$submissionId = $this->seedSubmissionWithSnapshot($selectId);
|
||||
|
||||
@@ -126,7 +126,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
// Step back over only the backfill migration → JSON columns repopulate
|
||||
// and snapshots revert to flat-string-array shape.
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->assertSame(0, DB::table('form_field_options')->count());
|
||||
|
||||
$select = DB::table('form_fields')->where('id', $selectId)->first();
|
||||
@@ -145,7 +145,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_options_present_on_non_option_field_type(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->seedFieldWithOptions('TAG_PICKER', ['Veiligheid', 'Horeca']);
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
@@ -155,7 +155,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_options_contains_non_string_entry(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode([
|
||||
['label' => 'A'],
|
||||
@@ -169,7 +169,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_options_is_object_shape(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode([
|
||||
'XS' => 'Extra small',
|
||||
@@ -183,7 +183,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_on_translations_length_mismatch(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode(['XS', 'S', 'M']), json_encode([
|
||||
'de' => ['options' => ['Klein', 'Mittel']], // 2 vs 3
|
||||
]));
|
||||
@@ -195,7 +195,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_on_non_string_translation(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode(['XS', 'S']), json_encode([
|
||||
'de' => ['options' => ['Klein', 42]],
|
||||
]));
|
||||
@@ -207,7 +207,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_on_oversized_translation(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->seedFieldWithOptionsRaw('SELECT', json_encode(['XS']), json_encode([
|
||||
'de' => ['options' => [str_repeat('x', 256)]],
|
||||
]));
|
||||
@@ -219,7 +219,7 @@ final class FormFieldOptionsBackfillTest extends TestCase
|
||||
|
||||
public function test_fails_when_snapshot_options_present_on_non_option_field_type(): void
|
||||
{
|
||||
$this->artisan('migrate:rollback', ['--step' => 1])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 2])->assertSuccessful();
|
||||
$this->seedTemplateWithSnapshotRaw([
|
||||
'fields' => [[
|
||||
'id' => (string) Str::ulid(),
|
||||
|
||||
@@ -31,15 +31,15 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
|
||||
public function test_forward_migration_backfills_rows_with_field_type_dispatch(): void
|
||||
{
|
||||
// Roll back: 2 WS-5d migrations (backfill-options, create-options) +
|
||||
// 4 WS-5c migrations (drop-conditional-logic-col,
|
||||
// Roll back: 3 WS-5d migrations (drop-options-cols, backfill-options,
|
||||
// create-options) + 4 WS-5c migrations (drop-conditional-logic-col,
|
||||
// backfill-conditional-logic, create-conditional-logic-conditions,
|
||||
// create-conditional-logic-groups) + 5 WS-5b migrations
|
||||
// (drop-cols + configs-backfill + create-configs +
|
||||
// validation-rules-backfill + create-validation-rules) = 11.
|
||||
// validation-rules-backfill + create-validation-rules) = 12.
|
||||
// Brings us to the pre-WS-5b state: validation_rules JSON column
|
||||
// present, no relational tables for WS-5b/c/d.
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
$this->assertFalse(Schema::hasTable('form_field_validation_rules'));
|
||||
$this->assertTrue(Schema::hasColumn('form_fields', 'validation_rules'));
|
||||
|
||||
@@ -100,7 +100,7 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
// (validation_rules JSON column present; no relational tables for
|
||||
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
|
||||
// + validation-rules-backfill + create-validation-rules = 5.
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
|
||||
$fieldId = $this->seedFieldWithJson([
|
||||
'field_type' => 'TAG_PICKER',
|
||||
@@ -124,7 +124,7 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
// (validation_rules JSON column present; no relational tables for
|
||||
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
|
||||
// + validation-rules-backfill + create-validation-rules = 5.
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
|
||||
$fieldId = $this->seedFieldWithJson([
|
||||
'field_type' => 'TEXT',
|
||||
@@ -151,7 +151,7 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
// (validation_rules JSON column present; no relational tables for
|
||||
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
|
||||
// + validation-rules-backfill + create-validation-rules = 5.
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithJson([
|
||||
'field_type' => 'TEXT',
|
||||
@@ -168,7 +168,7 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
// (validation_rules JSON column present; no relational tables for
|
||||
// WS-5b). Step count: drop-cols + configs-backfill + create-configs
|
||||
// + validation-rules-backfill + create-validation-rules = 5.
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
|
||||
$this->seedFieldWithJson([
|
||||
'field_type' => 'BOOLEAN',
|
||||
@@ -187,7 +187,7 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
// full-back-then-full-forward cycle — rolling back all WS-5b
|
||||
// migrations restores the pre-WS-5b state (columns present on
|
||||
// source tables; validation rules relational table gone).
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
[$numberId] = $this->seedFields();
|
||||
|
||||
$this->artisan('migrate')->assertSuccessful();
|
||||
@@ -202,7 +202,7 @@ final class FormFieldValidationRuleBackfillTest extends TestCase
|
||||
|
||||
// Roll back WS-5b fully → column reappears and carries canonical JSON
|
||||
// reconstructed from the relational rows.
|
||||
$this->artisan('migrate:rollback', ['--step' => 11])->assertSuccessful();
|
||||
$this->artisan('migrate:rollback', ['--step' => 12])->assertSuccessful();
|
||||
$this->assertTrue(Schema::hasColumn('form_fields', 'validation_rules'));
|
||||
|
||||
$field = DB::table('form_fields')->where('id', $numberId)->first();
|
||||
|
||||
Reference in New Issue
Block a user