Character: add ability to edit a character.
This commit is contained in:
parent
d8895901bf
commit
b6f1f8b887
8 changed files with 217 additions and 70 deletions
|
|
@ -28,6 +28,15 @@ class CharacterController extends Controller
|
|||
return view('character.create');
|
||||
}
|
||||
|
||||
public function edit(Character $character)
|
||||
{
|
||||
$this->authorize('update', $character);
|
||||
|
||||
return view('character.edit', [
|
||||
'character' => $character
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Character $character)
|
||||
{
|
||||
$this->authorize('delete', $character);
|
||||
|
|
|
|||
|
|
@ -8,41 +8,22 @@ use App\Warcraft\Races;
|
|||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateCharacterForm extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
/**
|
||||
* Character's name
|
||||
*/
|
||||
public string $name = '';
|
||||
|
||||
/**
|
||||
* Character's level
|
||||
*/
|
||||
public string $level = '70';
|
||||
|
||||
/**
|
||||
* All possible classes
|
||||
*/
|
||||
public $classes;
|
||||
|
||||
/**
|
||||
* Character's class
|
||||
*/
|
||||
public string $class = '';
|
||||
|
||||
/**
|
||||
* All possible races
|
||||
*/
|
||||
public $races;
|
||||
|
||||
/**
|
||||
* Character's race
|
||||
*/
|
||||
public string $race = '';
|
||||
|
||||
/**
|
||||
* All posible genders
|
||||
*/
|
||||
|
|
@ -52,28 +33,39 @@ class CreateCharacterForm extends Component
|
|||
];
|
||||
|
||||
/**
|
||||
* Character gender
|
||||
* Character model
|
||||
*/
|
||||
public $gender = 'M';
|
||||
public Character $character;
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected $rules = [
|
||||
'name' => 'required|alpha|min:2|max:12|unique:characters,name',
|
||||
'level' => 'required|integer|min:1|max:70',
|
||||
'gender' => 'required|in:M,F',
|
||||
'race' => 'required',
|
||||
'class' => 'required'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
public function rules()
|
||||
{
|
||||
$this->races = (new Races)->alliance();
|
||||
$rules['race'] = 'required|in:' . $this->races->keys()->join(',');
|
||||
return [
|
||||
'character.name' => [ 'required', 'min:4', Rule::unique('characters', 'name')->ignore($this->character) ],
|
||||
'character.level' => [ 'required', 'integer', 'min:1', 'max:70' ],
|
||||
'character.gender' => [ 'required', 'in:M,F' ],
|
||||
'character.race' => [ 'required', 'in:' . $this->races->keys()->join(',') ],
|
||||
'character.class' => [ 'required', 'in:' . $this->classes->keys()->join(',') ]
|
||||
];
|
||||
}
|
||||
|
||||
$this->race = $this->races->keys()->first();
|
||||
$this->updatedRace($this->race);
|
||||
public function mount(Character $character)
|
||||
{
|
||||
$this->character = $character;
|
||||
$this->races = (new Races)->alliance();
|
||||
|
||||
// Set some default values.
|
||||
if (!$this->character->exists) {
|
||||
$this->character->race = $this->races->keys()->first();
|
||||
$this->character->gender = 'M';
|
||||
$this->character->level = 70;
|
||||
}
|
||||
|
||||
$this->updatedCharacterRace($this->character->race);
|
||||
}
|
||||
|
||||
public function updated($propertyName)
|
||||
|
|
@ -81,18 +73,15 @@ class CreateCharacterForm extends Component
|
|||
$this->validateOnly($propertyName);
|
||||
}
|
||||
|
||||
public function updatedRace($race)
|
||||
public function updatedCharacterRace($race)
|
||||
{
|
||||
// Update classes list for this race.
|
||||
$this->classes = (new Classes)->race($this->race);
|
||||
|
||||
// Update validation rules
|
||||
$rules['class'] = 'required|in:' . $this->classes->keys()->join(',');
|
||||
$this->classes = (new Classes)->race($race);
|
||||
|
||||
// if this race can not be the selected class.
|
||||
// select the first one.
|
||||
if (!$this->classes->has($this->class)) {
|
||||
$this->class = $this->classes->keys()->first();
|
||||
if (!$this->classes->has($this->character->class)) {
|
||||
$this->character->class = $this->classes->keys()->first();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,16 +90,25 @@ class CreateCharacterForm extends Component
|
|||
*/
|
||||
public function save()
|
||||
{
|
||||
$this->authorize('create', Character::class);
|
||||
if ($this->character->exists) {
|
||||
$this->authorize('update', $this->character);
|
||||
$action = "updated";
|
||||
} else {
|
||||
$this->authorize('create', Character::class);
|
||||
$action = "created";
|
||||
}
|
||||
|
||||
$data = $this->validate();
|
||||
$this->validate();
|
||||
|
||||
$user = auth()->user();
|
||||
$character = $user->characters()->create($data);
|
||||
if ($action === 'created') {
|
||||
$this->character->user()
|
||||
->associate(auth()->user());
|
||||
}
|
||||
$this->character->save();
|
||||
|
||||
// Livewire redirect() does not have "with" method.
|
||||
// so we call session()->flash() directly instead.
|
||||
session()->flash('success', "<strong>{$character->name}</strong> was created!");
|
||||
session()->flash('success', "<strong>{$this->character->name}</strong> was {$action}!");
|
||||
return redirect()->route('user.index');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<x-slot name="title">{{ __('Add character') }}</x-slot>
|
||||
|
||||
<div class="p-6 mx-auto max-w-xl">
|
||||
<livewire:form.create-character-form>
|
||||
<livewire:form.create-character-form />
|
||||
</div>
|
||||
|
||||
</x-layout>
|
||||
|
|
|
|||
16
resources/views/character/edit.blade.php
Normal file
16
resources/views/character/edit.blade.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<x-layout name="app">
|
||||
|
||||
<x-slot name="title">
|
||||
<div class="flex">
|
||||
{{ __('Character') }} -
|
||||
<x-class-icon class="w-8 h-8 mx-2" :name="$character->class" />
|
||||
{{ $character->level }} {{ $character->name }}
|
||||
- {{ __('Edit') }}
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="p-6 mx-auto max-w-xl">
|
||||
<livewire:form.create-character-form :character="$character" />
|
||||
</div>
|
||||
|
||||
</x-layout>
|
||||
|
|
@ -5,34 +5,34 @@
|
|||
|
||||
<div>
|
||||
<x-form.label for="name">{{ __('Name') }}</x-form.label>
|
||||
<x-input wire:model="name" name="name" label="name" />
|
||||
<x-input wire:model="character.name" name="character.name" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-form.label for="level">{{ __('Level') }}</x-form.label>
|
||||
<x-input wire:model="level" name="level" label="level" />
|
||||
<x-input wire:model="character.level" name="character.level" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4 mt-2">
|
||||
<div>
|
||||
<x-form.label for="race">{{ __('Race') }}</x-form.label>
|
||||
<x-select wire:model="race" name="race" :options="$races" />
|
||||
<x-select wire:model="character.race" name="character.race" :options="$races" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-form.label for="gender">{{ __('Gender') }}</x-form.label>
|
||||
<x-select wire:model="gender" name="gender" :options="$genders" />
|
||||
<x-select wire:model="character.gender" name="character.gender" :options="$genders" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-form.label for="class" >{{ __('Class') }}</x-form.label>
|
||||
<x-select wire:model="class" name="class" :options="$classes" />
|
||||
<x-select wire:model="character.class" name="character.class" :options="$classes" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<x-button element="input" type="submit" value="Save" />
|
||||
<x-button element="input" type="submit" value="{{$this->character->exists ? 'Save' : 'Create' }}" />
|
||||
</div>
|
||||
|
||||
</x-form>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ Route::prefix('characters')->name('character.')->group(function() {
|
|||
});
|
||||
|
||||
Route::get('/', [CharacterController::class, 'show'])->name('show');
|
||||
Route::get('/edit', [CharacterController::class, 'edit'])->name('edit');
|
||||
Route::delete('/', [CharacterController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ class CharacterCreateTest extends TestCase
|
|||
$this->actingAs(User::factory()->create());
|
||||
|
||||
\Livewire::test(CreateCharacterForm::class)
|
||||
->set('name', 'Elise')
|
||||
->set('level', '70')
|
||||
->set('class', 'warrior')
|
||||
->set('race', 'human')
|
||||
->set('gender', 'F')
|
||||
->set('character.name', 'Elise')
|
||||
->set('character.level', '70')
|
||||
->set('character.class', 'warrior')
|
||||
->set('character.race', 'human')
|
||||
->set('character.gender', 'F')
|
||||
->call('save')
|
||||
->assertRedirect(route('user.index'));
|
||||
|
||||
|
|
@ -67,11 +67,11 @@ class CharacterCreateTest extends TestCase
|
|||
|
||||
// Try create one more via livewire form.
|
||||
\Livewire::test(CreateCharacterForm::class)
|
||||
->set('name', 'Notonemore')
|
||||
->set('level', '10')
|
||||
->set('class', 'mage')
|
||||
->set('race', 'troll')
|
||||
->set('gender', 'M')
|
||||
->set('character.name', 'Notonemore')
|
||||
->set('character.level', '10')
|
||||
->set('character.class', 'mage')
|
||||
->set('character.race', 'troll')
|
||||
->set('character.gender', 'M')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
}
|
||||
|
|
@ -79,11 +79,11 @@ class CharacterCreateTest extends TestCase
|
|||
public function test_guest_can_not_create_characters()
|
||||
{
|
||||
\Livewire::test(CreateCharacterForm::class)
|
||||
->set('name', 'Guestchar')
|
||||
->set('level', '61')
|
||||
->set('class', 'priest')
|
||||
->set('race', 'dwarf')
|
||||
->set('gender', 'M')
|
||||
->set('character.name', 'Guestchar')
|
||||
->set('character.level', '61')
|
||||
->set('character.class', 'priest')
|
||||
->set('character.race', 'dwarf')
|
||||
->set('character.gender', 'M')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
|
|
|
|||
123
tests/Feature/CharacterUpdateTest.php
Normal file
123
tests/Feature/CharacterUpdateTest.php
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Character;
|
||||
|
||||
use App\Http\Livewire\Form\CreateCharacterForm;
|
||||
|
||||
class CharacterUpdateTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_user_can_render_character_edit_page()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$character = Character::factory()->for($user)->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('character.edit', ['character' => $character]));
|
||||
|
||||
$response->assertStatus(200); // OK
|
||||
}
|
||||
|
||||
public function test_guest_can_not_render_character_edit_page_for_someone_elses_character()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$character = Character::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('character.edit', ['character' => $character]));
|
||||
|
||||
$response->assertStatus(403); // Forbidden
|
||||
}
|
||||
|
||||
public function test_user_can_update_character()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$character = Character::factory()->for($user)->create([
|
||||
'name' => 'Mychar',
|
||||
'level' => '60',
|
||||
'class' => 'warrior',
|
||||
'race' => 'human',
|
||||
'gender' => 'M'
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
\Livewire::test(CreateCharacterForm::class, ['character' => $character])
|
||||
->set('character.name', 'Updated')
|
||||
->set('character.level', '70')
|
||||
->set('character.class', 'priest')
|
||||
->set('character.race', 'dwarf')
|
||||
->set('character.gender', 'F')
|
||||
->call('save')
|
||||
->assertRedirect(route('user.index'));
|
||||
|
||||
// Check database that character was updated.
|
||||
$this->assertDatabaseHas('characters', [
|
||||
'name' => 'Updated',
|
||||
'level' => '70',
|
||||
'race' => 'dwarf',
|
||||
'class' => 'priest',
|
||||
'gender' => 'F'
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_not_update_someone_elses_character()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$character = Character::factory()->create([
|
||||
'race' => 'undead',
|
||||
'class' => 'rogue'
|
||||
]);;
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
// Try update via livewire form.
|
||||
\Livewire::test(CreateCharacterForm::class, ['character' => $character])
|
||||
->set('character.name', 'Mynewname')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
// Check database that character has old data
|
||||
$this->assertDatabaseHas('characters', [
|
||||
'name' => $character->name,
|
||||
'level' => $character->level,
|
||||
'race' => $character->race,
|
||||
'class' => $character->class,
|
||||
'gender' => $character->gender
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_guest_can_not_update_characters()
|
||||
{
|
||||
$character = Character::factory()->create([
|
||||
'race' => 'troll',
|
||||
'class' => 'mage'
|
||||
]);
|
||||
|
||||
\Livewire::test(CreateCharacterForm::class, ['character' => $character])
|
||||
->set('character.name', 'Guestchar')
|
||||
->set('character.level', '61')
|
||||
->set('character.class', 'priest')
|
||||
->set('character.race', 'dwarf')
|
||||
->set('character.gender', 'M')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
// Check database that character has old data
|
||||
$this->assertDatabaseHas('characters', [
|
||||
'name' => $character->name,
|
||||
'level' => $character->level,
|
||||
'race' => $character->race,
|
||||
'class' => $character->class,
|
||||
'gender' => $character->gender
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in a new issue