mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
Fix: Use fill() instead of rawState() when creating another inside CreateRecord (#19343)
* Refactor form state handling to use `fill` instead of `rawState`. * fix --------- Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
co-authored by
Dan Harrin
parent
41f0963057
commit
faaa5cab16
@@ -132,6 +132,10 @@ class CreateAction extends Action
|
||||
...$preserveRawState ?? [],
|
||||
]);
|
||||
|
||||
// Rebuild child schemas without double-firing `afterStateHydrated()` hooks.
|
||||
$hydratedDefaultState = null;
|
||||
$schema->hydrateState($hydratedDefaultState, shouldCallHydrationHooks: false);
|
||||
|
||||
$this->halt();
|
||||
|
||||
return;
|
||||
|
||||
@@ -143,6 +143,10 @@ class CreateRecord extends Page
|
||||
...$preserveRawState,
|
||||
]);
|
||||
|
||||
// Rebuild child schemas without double-firing `afterStateHydrated()` hooks.
|
||||
$hydratedDefaultState = null;
|
||||
$this->form->hydrateState($hydratedDefaultState, shouldCallHydrationHooks: false);
|
||||
|
||||
$this->isCreating = false;
|
||||
|
||||
return;
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\Testing\TestAction;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Tests\Fixtures\Models\Department;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Filament\Tests\Fixtures\Models\Ticket;
|
||||
use Filament\Tests\Fixtures\Models\User;
|
||||
use Filament\Tests\Fixtures\Resources\Tickets\Pages\EditTicket;
|
||||
use Filament\Tests\Fixtures\Resources\Tickets\RelationManagers\DepartmentsRelationManager;
|
||||
use Filament\Tests\Fixtures\Resources\Tickets\RelationManagers\DepartmentsRelationManagerWithPreservation;
|
||||
use Filament\Tests\Fixtures\Resources\Users\Pages\EditUser;
|
||||
use Filament\Tests\Fixtures\Resources\Users\RelationManagers\PostsWithCreateAndPreserveRepeaterRelationManager;
|
||||
use Filament\Tests\Fixtures\Resources\Users\RelationManagers\PostsWithCreateAndPreserveRepeaterWithDefaultRelationManager;
|
||||
use Filament\Tests\Panels\Resources\TestCase;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -102,6 +109,108 @@ it('can create another record using `CreateAction`', function (): void {
|
||||
assertDatabaseHas(Department::class, ['name' => $firstName]);
|
||||
});
|
||||
|
||||
it('can create another record and preserve data using `CreateAction`', function (): void {
|
||||
$ticket = Ticket::factory()->create();
|
||||
|
||||
livewire(DepartmentsRelationManagerWithPreservation::class, ['ownerRecord' => $ticket, 'pageClass' => EditTicket::class])
|
||||
->mountAction(TestAction::make(CreateAction::class)->table(), ['another' => true])
|
||||
->fillForm([
|
||||
'name' => $firstName = Str::random(),
|
||||
])
|
||||
->callMountedAction()
|
||||
->assertHasNoFormErrors()
|
||||
->assertSchemaStateSet([
|
||||
'name' => $firstName,
|
||||
])
|
||||
->fillForm([
|
||||
'name' => $secondName = Str::random(),
|
||||
])
|
||||
->callMountedAction()
|
||||
->assertHasNoFormErrors();
|
||||
|
||||
assertDatabaseHas(Department::class, ['name' => $firstName]);
|
||||
assertDatabaseHas(Department::class, ['name' => $secondName]);
|
||||
});
|
||||
|
||||
it('can create another record and preserve repeater data using `CreateAction`', function (): void {
|
||||
$undoRepeaterFake = Repeater::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$repeaterItems = [
|
||||
['name' => 'First Item', 'email' => 'first@example.com'],
|
||||
['name' => 'Second Item', 'email' => 'second@example.com'],
|
||||
];
|
||||
|
||||
livewire(PostsWithCreateAndPreserveRepeaterRelationManager::class, ['ownerRecord' => $user, 'pageClass' => EditUser::class])
|
||||
->mountAction(TestAction::make(CreateAction::class)->table(), ['another' => true])
|
||||
->fillForm([
|
||||
'title' => $firstTitle = Str::random(),
|
||||
'rating' => 5,
|
||||
'json_array_of_objects' => $repeaterItems,
|
||||
])
|
||||
->callMountedAction()
|
||||
->assertHasNoFormErrors()
|
||||
->fillForm([
|
||||
'title' => $secondTitle = Str::random(),
|
||||
'rating' => 3,
|
||||
])
|
||||
->callMountedAction()
|
||||
->assertHasNoFormErrors();
|
||||
|
||||
$record = Post::query()->where('title', $firstTitle)->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
expect($record->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$record2 = Post::query()->where('title', $secondTitle)->first();
|
||||
|
||||
expect($record2)->not->toBeNull();
|
||||
expect($record2->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$undoRepeaterFake();
|
||||
});
|
||||
|
||||
it('can create another record and preserve repeater data with `default()` values using `CreateAction`', function (): void {
|
||||
$undoRepeaterFake = Repeater::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$repeaterItems = [
|
||||
['name' => 'Custom A', 'email' => 'a@example.com'],
|
||||
['name' => 'Custom B', 'email' => 'b@example.com'],
|
||||
['name' => 'Custom C', 'email' => 'c@example.com'],
|
||||
];
|
||||
|
||||
livewire(PostsWithCreateAndPreserveRepeaterWithDefaultRelationManager::class, ['ownerRecord' => $user, 'pageClass' => EditUser::class])
|
||||
->mountAction(TestAction::make(CreateAction::class)->table(), ['another' => true])
|
||||
->fillForm([
|
||||
'title' => $firstTitle = Str::random(),
|
||||
'rating' => 5,
|
||||
'json_array_of_objects' => $repeaterItems,
|
||||
])
|
||||
->callMountedAction()
|
||||
->assertHasNoFormErrors()
|
||||
->fillForm([
|
||||
'title' => $secondTitle = Str::random(),
|
||||
'rating' => 3,
|
||||
])
|
||||
->callMountedAction()
|
||||
->assertHasNoFormErrors();
|
||||
|
||||
$record = Post::query()->where('title', $firstTitle)->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
expect($record->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$record2 = Post::query()->where('title', $secondTitle)->first();
|
||||
|
||||
expect($record2)->not->toBeNull();
|
||||
expect($record2->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$undoRepeaterFake();
|
||||
});
|
||||
|
||||
it('can cancel `CreateAction` without creating record', function (): void {
|
||||
$ticket = Ticket::factory()->create();
|
||||
$initialCount = Department::count();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Resources\Posts\Pages;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\PostResource;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CreateAnotherPreservingRepeaterPost extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
public function form(Schema $form): Schema
|
||||
{
|
||||
return $form
|
||||
->components([
|
||||
Forms\Components\TextInput::make('title')->required(),
|
||||
Forms\Components\TextInput::make('rating')->numeric()->required(),
|
||||
Forms\Components\Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
Forms\Components\Repeater::make('json_array_of_objects')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name'),
|
||||
Forms\Components\TextInput::make('email'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function preserveFormDataWhenCreatingAnother(array $data): array
|
||||
{
|
||||
return Arr::only($data, ['json_array_of_objects']);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Resources\Posts\Pages;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\PostResource;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CreateAnotherPreservingRepeaterWithDefaultPost extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
public function form(Schema $form): Schema
|
||||
{
|
||||
return $form
|
||||
->components([
|
||||
Forms\Components\TextInput::make('title')->required(),
|
||||
Forms\Components\TextInput::make('rating')->numeric()->required(),
|
||||
Forms\Components\Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
Forms\Components\Repeater::make('json_array_of_objects')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name'),
|
||||
Forms\Components\TextInput::make('email'),
|
||||
])
|
||||
->default([
|
||||
['name' => 'Default Name', 'email' => 'default@example.com'],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function preserveFormDataWhenCreatingAnother(array $data): array
|
||||
{
|
||||
return Arr::only($data, ['json_array_of_objects']);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Resources\Tickets\RelationManagers;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Resources\Departments\Schemas\DepartmentForm;
|
||||
use Filament\Tests\Fixtures\Resources\Departments\Tables\DepartmentsTable;
|
||||
|
||||
class DepartmentsRelationManagerWithPreservation extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'departments';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return DepartmentsTable::configure($table)
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->preserveFormDataWhenCreatingAnother(['name']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return DepartmentForm::configure($schema);
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Resources\Users\RelationManagers;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PostsWithCreateAndPreserveRepeaterRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'posts';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('title')
|
||||
->inverseRelationship('author')
|
||||
->columns([
|
||||
TextColumn::make('title'),
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->preserveFormDataWhenCreatingAnother(['json_array_of_objects']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('title')->required(),
|
||||
TextInput::make('rating')->numeric()->required(),
|
||||
Repeater::make('json_array_of_objects')
|
||||
->schema([
|
||||
TextInput::make('name'),
|
||||
TextInput::make('email'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Resources\Users\RelationManagers;
|
||||
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PostsWithCreateAndPreserveRepeaterWithDefaultRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'posts';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('title')
|
||||
->inverseRelationship('author')
|
||||
->columns([
|
||||
TextColumn::make('title'),
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->preserveFormDataWhenCreatingAnother(['json_array_of_objects']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('title')->required(),
|
||||
TextInput::make('rating')->numeric()->required(),
|
||||
Repeater::make('json_array_of_objects')
|
||||
->schema([
|
||||
TextInput::make('name'),
|
||||
TextInput::make('email'),
|
||||
])
|
||||
->default([
|
||||
['name' => 'Default Name', 'email' => 'default@example.com'],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Resources\Events\RecordCreated;
|
||||
use Filament\Resources\Events\RecordSaved;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Filament\Tests\Fixtures\Policies\TicketPolicy;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\CreateAnotherPreservingDataPost;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\CreateAnotherPreservingRepeaterPost;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\CreateAnotherPreservingRepeaterWithDefaultPost;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\CreatePost;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\PostResource;
|
||||
use Filament\Tests\Fixtures\Resources\TicketMessages\TicketMessageResource;
|
||||
@@ -181,6 +184,105 @@ it('can create another and preserve data', function (): void {
|
||||
expect($record2->tags)->toBe($newData->tags);
|
||||
});
|
||||
|
||||
it('can create another and preserve repeater data', function (): void {
|
||||
$undoRepeaterFake = Repeater::fake();
|
||||
|
||||
$newData = Post::factory()->make();
|
||||
$newData2 = Post::factory()->make();
|
||||
|
||||
$repeaterItems = [
|
||||
['name' => 'First Item', 'email' => 'first@example.com'],
|
||||
['name' => 'Second Item', 'email' => 'second@example.com'],
|
||||
];
|
||||
|
||||
livewire(CreateAnotherPreservingRepeaterPost::class)
|
||||
->fillForm([
|
||||
'author_id' => $newData->author->getKey(),
|
||||
'title' => $newData->title,
|
||||
'rating' => $newData->rating,
|
||||
'json_array_of_objects' => $repeaterItems,
|
||||
])
|
||||
->call('create', true)
|
||||
->assertHasNoFormErrors()
|
||||
->assertNoRedirect()
|
||||
->fillForm([
|
||||
'author_id' => $newData2->author->getKey(),
|
||||
'title' => $newData2->title,
|
||||
'rating' => $newData2->rating,
|
||||
])
|
||||
->call('create')
|
||||
->assertHasNoFormErrors()
|
||||
->assertRedirect();
|
||||
|
||||
$record = Post::query()
|
||||
->where('title', $newData->title)
|
||||
->where('rating', $newData->rating)
|
||||
->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
expect($record->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$record2 = Post::query()
|
||||
->where('title', $newData2->title)
|
||||
->where('rating', $newData2->rating)
|
||||
->first();
|
||||
|
||||
expect($record2)->not->toBeNull();
|
||||
expect($record2->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$undoRepeaterFake();
|
||||
});
|
||||
|
||||
it('can create another and preserve repeater data with `default()` values', function (): void {
|
||||
$undoRepeaterFake = Repeater::fake();
|
||||
|
||||
$newData = Post::factory()->make();
|
||||
$newData2 = Post::factory()->make();
|
||||
|
||||
$repeaterItems = [
|
||||
['name' => 'Custom Item A', 'email' => 'a@example.com'],
|
||||
['name' => 'Custom Item B', 'email' => 'b@example.com'],
|
||||
['name' => 'Custom Item C', 'email' => 'c@example.com'],
|
||||
];
|
||||
|
||||
livewire(CreateAnotherPreservingRepeaterWithDefaultPost::class)
|
||||
->fillForm([
|
||||
'author_id' => $newData->author->getKey(),
|
||||
'title' => $newData->title,
|
||||
'rating' => $newData->rating,
|
||||
'json_array_of_objects' => $repeaterItems,
|
||||
])
|
||||
->call('create', true)
|
||||
->assertHasNoFormErrors()
|
||||
->assertNoRedirect()
|
||||
->fillForm([
|
||||
'author_id' => $newData2->author->getKey(),
|
||||
'title' => $newData2->title,
|
||||
'rating' => $newData2->rating,
|
||||
])
|
||||
->call('create')
|
||||
->assertHasNoFormErrors()
|
||||
->assertRedirect();
|
||||
|
||||
$record = Post::query()
|
||||
->where('title', $newData->title)
|
||||
->where('rating', $newData->rating)
|
||||
->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
expect($record->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$record2 = Post::query()
|
||||
->where('title', $newData2->title)
|
||||
->where('rating', $newData2->rating)
|
||||
->first();
|
||||
|
||||
expect($record2)->not->toBeNull();
|
||||
expect($record2->json_array_of_objects)->toBe($repeaterItems);
|
||||
|
||||
$undoRepeaterFake();
|
||||
});
|
||||
|
||||
it('can validate input', function (): void {
|
||||
Post::factory()->make();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user