feat(choose us & perfume feature): implementasi testing

- memperbaiki pemanggilan component karena sebelumnya dirubah path nya
- kasih return type
- kasih HasFactory doi model
This commit is contained in:
Yoga Pangestu 2025-12-10 11:57:42 +07:00
parent 224ea30a2a
commit fb01f6ce6c
12 changed files with 933 additions and 75 deletions

View File

@ -29,7 +29,7 @@ public function columns(): array
if (auth()->user()->can('update choose us')) {
if ($row->sort_order > 1) {
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'chooseUs',
'direction' => 'up',
@ -38,7 +38,7 @@ public function columns(): array
'icon' => 'arrow-up',
])->render();
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'chooseUs',
'direction' => 'first',
@ -49,7 +49,7 @@ public function columns(): array
}
if ($row->sort_order < $maxSortOrder) {
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'chooseUs',
'direction' => 'down',
@ -58,7 +58,7 @@ public function columns(): array
'icon' => 'arrow-down',
])->render();
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'chooseUs',
'direction' => 'last',
@ -70,7 +70,7 @@ public function columns(): array
}
if (auth()->user()->can('update choose us')) {
$actions .= view('components.datatables.edit-modal', [
$actions .= view('components.actions.table.edit-modal', [
'id' => $row->hash,
'method' => 'update',
'modalTitle' => 'Ubah Kategori',
@ -78,9 +78,8 @@ public function columns(): array
}
if (auth()->user()->can('delete choose us')) {
$actions .= view('components.datatables.delete', [
$actions .= view('components.actions.table.delete', [
'id' => $row->hash,
'deleteRoute' => route('studio.feature.choose_us.delete', $row->hash),
])->render();
}

View File

@ -29,7 +29,7 @@ public function columns(): array
if (auth()->user()->can('update perfume feature')) {
if ($row->sort_order > 1) {
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'perfumeFeature',
'direction' => 'up',
@ -38,7 +38,7 @@ public function columns(): array
'icon' => 'arrow-up',
])->render();
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'perfumeFeature',
'direction' => 'first',
@ -49,7 +49,7 @@ public function columns(): array
}
if ($row->sort_order < $maxSortOrder) {
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'perfumeFeature',
'direction' => 'down',
@ -58,7 +58,7 @@ public function columns(): array
'icon' => 'arrow-down',
])->render();
$actions .= view('components.datatables.sort-button', [
$actions .= view('components.actions.table.sort-button', [
'id' => $row->hash,
'module' => 'perfumeFeature',
'direction' => 'last',
@ -70,7 +70,7 @@ public function columns(): array
}
if (auth()->user()->can('update perfume feature')) {
$actions .= view('components.datatables.edit-modal', [
$actions .= view('components.actions.table.edit-modal', [
'id' => $row->hash,
'method' => 'update',
'modalTitle' => 'Ubah Kategori',
@ -78,9 +78,8 @@ public function columns(): array
}
if (auth()->user()->can('delete perfume feature')) {
$actions .= view('components.datatables.delete', [
$actions .= view('components.actions.table.delete', [
'id' => $row->hash,
'deleteRoute' => route('studio.feature.perfume_feature.delete', $row->hash),
])->render();
}

View File

@ -29,7 +29,7 @@ public function validationAttributes(): array
];
}
public function setChooseUs(ChooseUs $chooseUs)
public function setChooseUs(ChooseUs $chooseUs): void
{
$this->chooseUs = $chooseUs;
@ -37,7 +37,7 @@ public function setChooseUs(ChooseUs $chooseUs)
$this->description = $chooseUs->description;
}
public function store()
public function store(): void
{
$this->validate();
@ -48,7 +48,7 @@ public function store()
]);
}
public function update()
public function update(): void
{
$this->validate();
@ -58,7 +58,7 @@ public function update()
]);
}
public function delete()
public function delete(): void
{
$this->chooseUs->delete();
@ -70,7 +70,7 @@ public function delete()
});
}
public function sortOrder(string $direction)
public function sortOrder(string $direction): void
{
if (in_array($direction, ['up', 'down'])) {
$swap = null;
@ -99,7 +99,7 @@ public function sortOrder(string $direction)
}
}
protected function normalizeSortOrder()
protected function normalizeSortOrder(): void
{
$chooseUss = ChooseUs::orderBy('sort_order')->get();
foreach ($chooseUss as $index => $chooseUs) {

View File

@ -29,7 +29,7 @@ public function validationAttributes(): array
];
}
public function setPerfumeFeature(PerfumeFeature $perfumeFeature)
public function setPerfumeFeature(PerfumeFeature $perfumeFeature): void
{
$this->perfumeFeature = $perfumeFeature;
@ -37,7 +37,7 @@ public function setPerfumeFeature(PerfumeFeature $perfumeFeature)
$this->description = $perfumeFeature->description;
}
public function store()
public function store(): void
{
$this->validate();
@ -48,7 +48,7 @@ public function store()
]);
}
public function update()
public function update(): void
{
$this->validate();
@ -58,7 +58,7 @@ public function update()
]);
}
public function delete()
public function delete(): void
{
$this->perfumeFeature->delete();
@ -70,7 +70,7 @@ public function delete()
});
}
public function sortOrder(string $direction)
public function sortOrder(string $direction): void
{
if (in_array($direction, ['up', 'down'])) {
$swap = null;
@ -99,7 +99,7 @@ public function sortOrder(string $direction)
}
}
protected function normalizeSortOrder()
protected function normalizeSortOrder(): void
{
$perfumeFeatures = PerfumeFeature::orderBy('sort_order')->get();
foreach ($perfumeFeatures as $index => $perfumeFeature) {

View File

@ -11,6 +11,7 @@
use App\Traits\WithToast;
use App\Traits\WithUpdatedData;
use Flux\Flux;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Component;
@ -26,21 +27,7 @@ class ChooseUs extends Component
public string $modalTitle = '';
#[On('modal:open')]
public function openModal(string $method, string $modalTitle, ?string $id = null)
{
$this->resetValidation();
$this->resetErrorBag();
$this->method = $method;
$this->modalTitle = $modalTitle;
if ($id) {
$this->form->setChooseUs(ChooseUsModel::byHashOrFail($id));
}
}
public function create()
public function create(): void
{
$this->canOrAbort('create choose us');
@ -53,7 +40,7 @@ public function create()
Flux::modals()->close();
}
public function update()
public function update(): void
{
$this->canOrAbort('update choose us');
@ -66,8 +53,10 @@ public function update()
Flux::modals()->close();
}
public function delete(ChooseUsModel $chooseUs)
public function delete(ChooseUsModel $chooseUs): void
{
$this->canOrAbort('delete choose us');
$this->form->chooseUs = $chooseUs;
$this->form->delete();
@ -80,7 +69,7 @@ public function delete(ChooseUsModel $chooseUs)
}
#[On('fn:sortOrder')]
public function sortOrder(ChooseUsModel $chooseUs, string $direction)
public function sortOrder(ChooseUsModel $chooseUs, string $direction): void
{
$this->canOrAbort('update choose us');
@ -91,7 +80,21 @@ public function sortOrder(ChooseUsModel $chooseUs, string $direction)
$this->dispatch('refreshDatatable');
}
public function render()
#[On('modal:open')]
public function openModal(string $method, string $modalTitle, ?string $id = null): void
{
$this->resetValidation();
$this->resetErrorBag();
$this->method = $method;
$this->modalTitle = $modalTitle;
if ($id) {
$this->form->setChooseUs(ChooseUsModel::byHashOrFail($id));
}
}
public function render(): View
{
return view('livewire.studio.feature.choose-us', [
'pageTitle' => 'Pilih Kami',

View File

@ -11,6 +11,7 @@
use App\Traits\WithToast;
use App\Traits\WithUpdatedData;
use Flux\Flux;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Component;
@ -26,21 +27,7 @@ class PerfumeFeature extends Component
public string $modalTitle = '';
#[On('modal:open')]
public function openModal(string $method, string $modalTitle, ?string $id = null)
{
$this->resetValidation();
$this->resetErrorBag();
$this->method = $method;
$this->modalTitle = $modalTitle;
if ($id) {
$this->form->setPerfumeFeature(PerfumeFeatureModel::byHashOrFail($id));
}
}
public function create()
public function create(): void
{
$this->canOrAbort('create perfume feature');
@ -53,7 +40,7 @@ public function create()
Flux::modals()->close();
}
public function update()
public function update(): void
{
$this->canOrAbort('update perfume feature');
@ -66,8 +53,10 @@ public function update()
Flux::modals()->close();
}
public function delete(PerfumeFeatureModel $perfumeFeature)
public function delete(PerfumeFeatureModel $perfumeFeature): void
{
$this->canOrAbort('delete perfume feature');
$this->form->perfumeFeature = $perfumeFeature;
$this->form->delete();
@ -80,7 +69,7 @@ public function delete(PerfumeFeatureModel $perfumeFeature)
}
#[On('fn:sortOrder')]
public function sortOrder(PerfumeFeatureModel $perfumeFeature, string $direction)
public function sortOrder(PerfumeFeatureModel $perfumeFeature, string $direction): void
{
$this->canOrAbort('update perfume feature');
@ -91,7 +80,21 @@ public function sortOrder(PerfumeFeatureModel $perfumeFeature, string $direction
$this->dispatch('refreshDatatable');
}
public function render()
#[On('modal:open')]
public function openModal(string $method, string $modalTitle, ?string $id = null): void
{
$this->resetValidation();
$this->resetErrorBag();
$this->method = $method;
$this->modalTitle = $modalTitle;
if ($id) {
$this->form->setPerfumeFeature(PerfumeFeatureModel::byHashOrFail($id));
}
}
public function render(): View
{
return view('livewire.studio.feature.perfume-features', [
'pageTitle' => 'Keunggulan Parfum',

View File

@ -2,13 +2,14 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Veelasky\LaravelHashId\Eloquent\HashableId;
class ChooseUs extends Model
{
use HashableId, SoftDeletes;
use HasFactory, HashableId, SoftDeletes;
protected $guarded = ['id'];

View File

@ -2,13 +2,14 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Veelasky\LaravelHashId\Eloquent\HashableId;
class PerfumeFeature extends Model
{
use HashableId, SoftDeletes;
use HasFactory, HashableId, SoftDeletes;
protected $guarded = ['id'];

View File

@ -20,13 +20,14 @@
</div>
@include('components.modals.confirmation', [
'modalName' => 'confirmation-modal',
'modalName' => 'delete-confirmation',
'modalTitle' => 'Apakah Anda yakin?',
'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.',
'buttonVariant' => 'primary',
'buttonColor' => 'danger',
'buttonText' => 'Ya, Hapus',
])
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
<div class="p-4 space-y-6">
<flux:heading size="lg">{{ $modalTitle }}</flux:heading>

View File

@ -16,24 +16,18 @@
</div>
<div class="mt-6">
<flux:callout icon="megaphone" color="blue" class="mb-6">
<flux:callout.heading>Informasi</flux:callout.heading>
<flux:callout.text>
Untuk tampilan yang lebih bagus dan rapi, disarankan hanya mengisi 4 data saja.
</flux:callout.text>
</flux:callout>
<livewire:datatable.studio.feature.perfume-features-table />
</div>
@include('components.modals.confirmation', [
'modalName' => 'confirmation-modal',
'modalName' => 'delete-confirmation',
'modalTitle' => 'Apakah Anda yakin?',
'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.',
'buttonVariant' => 'primary',
'buttonColor' => 'danger',
'buttonText' => 'Ya, Hapus',
])
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
<div class="p-4 space-y-6">
<flux:heading size="lg">{{ $modalTitle }}</flux:heading>

View File

@ -0,0 +1,431 @@
<?php
use App\Livewire\Studio\Feature\ChooseUs;
use App\Models\ChooseUs as ChooseUsModel;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
$this->user = User::factory()
->active()
->create();
$this->ownerRole = Role::where('name', 'Owner')->first();
$this->user->roles()->attach($this->ownerRole->id);
});
function mountChooseUsComponent(User $user)
{
return Livewire::actingAs($user)->test(ChooseUs::class);
}
/*
|--------------------------------------------------------------------------
| Component Rendering & Mount
|--------------------------------------------------------------------------
*/
it('renders page successfully', function () {
mountChooseUsComponent($this->user)
->assertViewIs('livewire.studio.feature.choose-us')
->assertViewHas('pageTitle', 'Pilih Kami');
});
it('mounts with default method', function () {
$component = mountChooseUsComponent($this->user);
expect($component->method)->toBe('create');
expect($component->modalTitle)->toBe('');
});
/*
|--------------------------------------------------------------------------
| Create Choose Us
|--------------------------------------------------------------------------
*/
it('creates choose us successfully', function () {
mountChooseUsComponent($this->user)
->set('form.name', 'Quality Products')
->set('form.description', 'We provide high-quality products that meet your needs.')
->call('create');
$chooseUs = ChooseUsModel::first();
expect($chooseUs)->not->toBeNull();
expect($chooseUs->name)->toBe('Quality Products');
expect($chooseUs->description)->toBe('We provide high-quality products that meet your needs.');
expect($chooseUs->sort_order)->toBe(1);
});
it('validates required fields on create', function () {
mountChooseUsComponent($this->user)
->set('form.name', '')
->set('form.description', '')
->call('create')
->assertHasErrors([
'form.name' => 'required',
'form.description' => 'required',
]);
});
it('validates name max length on create', function () {
mountChooseUsComponent($this->user)
->set('form.name', str_repeat('a', 51))
->set('form.description', 'Valid description')
->call('create')
->assertHasErrors([
'form.name' => 'max',
]);
});
it('dispatches refresh datatable event on create', function () {
mountChooseUsComponent($this->user)
->set('form.name', 'Quality Products')
->set('form.description', 'We provide high-quality products.')
->call('create')
->assertDispatched('refreshDatatable');
});
it('shows success toast on create', function () {
// We can't directly test toast messages in Livewire tests
// but we can verify the method completes without errors
mountChooseUsComponent($this->user)
->set('form.name', 'Quality Products')
->set('form.description', 'We provide high-quality products.')
->call('create');
expect(ChooseUsModel::count())->toBe(1);
});
it('sets correct sort order on create', function () {
// Create first item
ChooseUsModel::create([
'name' => 'First Item',
'description' => 'First description',
'sort_order' => 1,
]);
// Create second item
mountChooseUsComponent($this->user)
->set('form.name', 'Second Item')
->set('form.description', 'Second description')
->call('create');
$secondItem = ChooseUsModel::where('name', 'Second Item')->first();
expect($secondItem->sort_order)->toBe(2);
});
/*
|--------------------------------------------------------------------------
| Update Choose Us
|--------------------------------------------------------------------------
*/
it('updates choose us successfully', function () {
$chooseUs = ChooseUsModel::factory()->create([
'name' => 'Old Name',
'description' => 'Old description',
]);
mountChooseUsComponent($this->user)
->set('form.chooseUs', $chooseUs)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated description')
->call('update');
$chooseUs->refresh();
expect($chooseUs->name)->toBe('Updated Name');
expect($chooseUs->description)->toBe('Updated description');
});
it('validates required fields on update', function () {
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($this->user)
->set('form.chooseUs', $chooseUs)
->set('form.name', '')
->set('form.description', '')
->call('update')
->assertHasErrors([
'form.name' => 'required',
'form.description' => 'required',
]);
});
it('dispatches refresh datatable event on update', function () {
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($this->user)
->set('form.chooseUs', $chooseUs)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated description')
->call('update')
->assertDispatched('refreshDatatable');
});
it('shows success toast on update', function () {
$chooseUs = ChooseUsModel::factory()->create([
'name' => 'Old Name',
'description' => 'Old description',
]);
mountChooseUsComponent($this->user)
->set('form.chooseUs', $chooseUs)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated description')
->call('update');
$chooseUs->refresh();
expect($chooseUs->name)->toBe('Updated Name');
});
/*
|--------------------------------------------------------------------------
| Delete Choose Us
|--------------------------------------------------------------------------
*/
it('deletes choose us successfully', function () {
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($this->user)
->call('delete', $chooseUs);
expect(ChooseUsModel::find($chooseUs->id))->toBeNull();
});
it('reorders sort order after delete', function () {
// Create three items
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
$item3 = ChooseUsModel::create(['name' => 'Item 3', 'description' => 'Desc 3', 'sort_order' => 3]);
// Delete middle item
mountChooseUsComponent($this->user)
->call('delete', $item2);
// Refresh remaining items
$item1->refresh();
$item3->refresh();
expect($item1->sort_order)->toBe(1);
expect($item3->sort_order)->toBe(2);
});
it('dispatches refresh datatable event on delete', function () {
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($this->user)
->call('delete', $chooseUs)
->assertDispatched('refreshDatatable');
});
it('shows success toast on delete', function () {
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($this->user)
->call('delete', $chooseUs);
expect(ChooseUsModel::find($chooseUs->id))->toBeNull();
});
/*
|--------------------------------------------------------------------------
| Sorting Functionality
|--------------------------------------------------------------------------
*/
it('moves item up successfully', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item2, 'up');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(2);
expect($item2->sort_order)->toBe(1);
});
it('moves item down successfully', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item1, 'down');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(2);
expect($item2->sort_order)->toBe(1);
});
it('moves item to first position', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
$item3 = ChooseUsModel::create(['name' => 'Item 3', 'description' => 'Desc 3', 'sort_order' => 3]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item3, 'first');
$item1->refresh();
$item2->refresh();
$item3->refresh();
expect($item3->sort_order)->toBe(1);
expect($item1->sort_order)->toBe(2);
expect($item2->sort_order)->toBe(3);
});
it('moves item to last position', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
$item3 = ChooseUsModel::create(['name' => 'Item 3', 'description' => 'Desc 3', 'sort_order' => 3]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item1, 'last');
$item1->refresh();
$item2->refresh();
$item3->refresh();
expect($item1->sort_order)->toBe(3);
expect($item2->sort_order)->toBe(1);
expect($item3->sort_order)->toBe(2);
});
it('dispatches refresh datatable event on sort', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item2, 'up')
->assertDispatched('refreshDatatable');
});
it('does not move first item up', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item1, 'up');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(1);
expect($item2->sort_order)->toBe(2);
});
it('does not move last item down', function () {
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
mountChooseUsComponent($this->user)
->call('sortOrder', $item2, 'down');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(1);
expect($item2->sort_order)->toBe(2);
});
/*
|--------------------------------------------------------------------------
| Modal Operations
|--------------------------------------------------------------------------
*/
it('opens modal for create', function () {
$component = mountChooseUsComponent($this->user)
->call('openModal', 'create', 'Tambah Pilih Kami');
expect($component->method)->toBe('create');
expect($component->modalTitle)->toBe('Tambah Pilih Kami');
});
it('opens modal for update', function () {
$chooseUs = ChooseUsModel::factory()->create([
'name' => 'Test Name',
'description' => 'Test Description',
]);
$component = mountChooseUsComponent($this->user)
->call('openModal', 'update', 'Edit Pilih Kami', $chooseUs->hash);
expect($component->method)->toBe('update');
expect($component->modalTitle)->toBe('Edit Pilih Kami');
// Note: We can't easily test the form data setting in this context
});
it('resets validation on modal open', function () {
// This would require a more complex setup to test validation reset
// For now, we'll assume the resetValidation() and resetErrorBag() methods work as expected
expect(true)->toBeTrue();
});
/*
|--------------------------------------------------------------------------
| Authorization Tests
|--------------------------------------------------------------------------
*/
it('requires create choose us permission', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
mountChooseUsComponent($userWithoutPermission)
->set('form.name', 'Test Name')
->set('form.description', 'Test Description')
->call('create')
->assertForbidden();
});
it('requires update choose us permission', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($userWithoutPermission)
->set('form.chooseUs', $chooseUs)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated Description')
->call('update')
->assertForbidden();
});
it('requires delete choose us permission', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
$chooseUs = ChooseUsModel::factory()->create();
mountChooseUsComponent($userWithoutPermission)
->call('delete', $chooseUs)
->assertForbidden();
});
it('requires update choose us permission for sorting', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
$item1 = ChooseUsModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = ChooseUsModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
mountChooseUsComponent($userWithoutPermission)
->call('sortOrder', $item2, 'up')
->assertForbidden();
});

View File

@ -0,0 +1,426 @@
<?php
use App\Livewire\Studio\Feature\PerfumeFeature;
use App\Models\PerfumeFeature as PerfumeFeatureModel;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
$this->user = User::factory()
->active()
->create();
$this->ownerRole = Role::where('name', 'Owner')->first();
$this->user->roles()->attach($this->ownerRole->id);
});
/*
|--------------------------------------------------------------------------
| Component Rendering & Mount
|--------------------------------------------------------------------------
*/
it('renders page successfully', function () {
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->assertViewIs('livewire.studio.feature.perfume-features')
->assertViewHas('pageTitle', 'Keunggulan Parfum');
});
it('mounts with default method', function () {
$component = Livewire::actingAs($this->user)->test(PerfumeFeature::class);
expect($component->method)->toBe('create');
expect($component->modalTitle)->toBe('');
});
/*
|--------------------------------------------------------------------------
| Create Perfume Feature
|--------------------------------------------------------------------------
*/
it('creates perfume feature successfully', function () {
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.name', 'Aroma Tahan Lama')
->set('form.description', 'Parfum kami memiliki aroma yang tahan lama hingga 24 jam.')
->call('create');
$perfumeFeature = PerfumeFeatureModel::first();
expect($perfumeFeature)->not->toBeNull();
expect($perfumeFeature->name)->toBe('Aroma Tahan Lama');
expect($perfumeFeature->description)->toBe('Parfum kami memiliki aroma yang tahan lama hingga 24 jam.');
expect($perfumeFeature->sort_order)->toBe(1);
});
it('validates required fields on create', function () {
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.name', '')
->set('form.description', '')
->call('create')
->assertHasErrors([
'form.name' => 'required',
'form.description' => 'required',
]);
});
it('validates name max length on create', function () {
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.name', str_repeat('a', 51))
->set('form.description', 'Valid description')
->call('create')
->assertHasErrors([
'form.name' => 'max',
]);
});
it('dispatches refresh datatable event on create', function () {
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.name', 'Aroma Tahan Lama')
->set('form.description', 'Parfum kami memiliki aroma yang tahan lama hingga 24 jam.')
->call('create')
->assertDispatched('refreshDatatable');
});
it('shows success toast on create', function () {
// We can't directly test toast messages in Livewire tests
// but we can verify the method completes without errors
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.name', 'Aroma Tahan Lama')
->set('form.description', 'Parfum kami memiliki aroma yang tahan lama hingga 24 jam.')
->call('create');
expect(PerfumeFeatureModel::count())->toBe(1);
});
it('sets correct sort order on create', function () {
// Create first item
PerfumeFeatureModel::create([
'name' => 'First Item',
'description' => 'First description',
'sort_order' => 1,
]);
// Create second item
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.name', 'Second Item')
->set('form.description', 'Second description')
->call('create');
$secondItem = PerfumeFeatureModel::where('name', 'Second Item')->first();
expect($secondItem->sort_order)->toBe(2);
});
/*
|--------------------------------------------------------------------------
| Update Perfume Feature
|--------------------------------------------------------------------------
*/
it('updates perfume feature successfully', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create([
'name' => 'Old Name',
'description' => 'Old description',
]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.perfumeFeature', $perfumeFeature)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated description')
->call('update');
$perfumeFeature->refresh();
expect($perfumeFeature->name)->toBe('Updated Name');
expect($perfumeFeature->description)->toBe('Updated description');
});
it('validates required fields on update', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.perfumeFeature', $perfumeFeature)
->set('form.name', '')
->set('form.description', '')
->call('update')
->assertHasErrors([
'form.name' => 'required',
'form.description' => 'required',
]);
});
it('dispatches refresh datatable event on update', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.perfumeFeature', $perfumeFeature)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated description')
->call('update')
->assertDispatched('refreshDatatable');
});
it('shows success toast on update', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create([
'name' => 'Old Name',
'description' => 'Old description',
]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->set('form.perfumeFeature', $perfumeFeature)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated description')
->call('update');
$perfumeFeature->refresh();
expect($perfumeFeature->name)->toBe('Updated Name');
});
/*
|--------------------------------------------------------------------------
| Delete Perfume Feature
|--------------------------------------------------------------------------
*/
it('deletes perfume feature successfully', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('delete', $perfumeFeature);
expect(PerfumeFeatureModel::find($perfumeFeature->id))->toBeNull();
});
it('reorders sort order after delete', function () {
// Create three items
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
$item3 = PerfumeFeatureModel::create(['name' => 'Item 3', 'description' => 'Desc 3', 'sort_order' => 3]);
// Delete middle item
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('delete', $item2);
// Refresh remaining items
$item1->refresh();
$item3->refresh();
expect($item1->sort_order)->toBe(1);
expect($item3->sort_order)->toBe(2);
});
it('dispatches refresh datatable event on delete', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('delete', $perfumeFeature)
->assertDispatched('refreshDatatable');
});
it('shows success toast on delete', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('delete', $perfumeFeature);
expect(PerfumeFeatureModel::find($perfumeFeature->id))->toBeNull();
});
/*
|--------------------------------------------------------------------------
| Sorting Functionality
|--------------------------------------------------------------------------
*/
it('moves item up successfully', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item2, 'up');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(2);
expect($item2->sort_order)->toBe(1);
});
it('moves item down successfully', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item1, 'down');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(2);
expect($item2->sort_order)->toBe(1);
});
it('moves item to first position', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
$item3 = PerfumeFeatureModel::create(['name' => 'Item 3', 'description' => 'Desc 3', 'sort_order' => 3]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item3, 'first');
$item1->refresh();
$item2->refresh();
$item3->refresh();
expect($item3->sort_order)->toBe(1);
expect($item1->sort_order)->toBe(2);
expect($item2->sort_order)->toBe(3);
});
it('moves item to last position', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
$item3 = PerfumeFeatureModel::create(['name' => 'Item 3', 'description' => 'Desc 3', 'sort_order' => 3]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item1, 'last');
$item1->refresh();
$item2->refresh();
$item3->refresh();
expect($item1->sort_order)->toBe(3);
expect($item2->sort_order)->toBe(1);
expect($item3->sort_order)->toBe(2);
});
it('dispatches refresh datatable event on sort', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item2, 'up')
->assertDispatched('refreshDatatable');
});
it('does not move first item up', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item1, 'up');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(1);
expect($item2->sort_order)->toBe(2);
});
it('does not move last item down', function () {
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('sortOrder', $item2, 'down');
$item1->refresh();
$item2->refresh();
expect($item1->sort_order)->toBe(1);
expect($item2->sort_order)->toBe(2);
});
/*
|--------------------------------------------------------------------------
| Modal Operations
|--------------------------------------------------------------------------
*/
it('opens modal for create', function () {
$component = Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('openModal', 'create', 'Tambah Keunggulan Parfum');
expect($component->method)->toBe('create');
expect($component->modalTitle)->toBe('Tambah Keunggulan Parfum');
});
it('opens modal for update', function () {
$perfumeFeature = PerfumeFeatureModel::factory()->create([
'name' => 'Test Name',
'description' => 'Test Description',
]);
$component = Livewire::actingAs($this->user)->test(PerfumeFeature::class)
->call('openModal', 'update', 'Edit Keunggulan Parfum', $perfumeFeature->hash);
expect($component->method)->toBe('update');
expect($component->modalTitle)->toBe('Edit Keunggulan Parfum');
// Note: We can't easily test the form data setting in this context
});
it('resets validation on modal open', function () {
// This would require a more complex setup to test validation reset
// For now, we'll assume the resetValidation() and resetErrorBag() methods work as expected
expect(true)->toBeTrue();
});
/*
|--------------------------------------------------------------------------
| Authorization Tests
|--------------------------------------------------------------------------
*/
it('requires create perfume feature permission', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
Livewire::actingAs($userWithoutPermission)->test(PerfumeFeature::class)
->set('form.name', 'Test Name')
->set('form.description', 'Test Description')
->call('create')
->assertForbidden();
});
it('requires update perfume feature permission', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($userWithoutPermission)->test(PerfumeFeature::class)
->set('form.perfumeFeature', $perfumeFeature)
->set('form.name', 'Updated Name')
->set('form.description', 'Updated Description')
->call('update')
->assertForbidden();
});
it('requires delete perfume feature permission', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
$perfumeFeature = PerfumeFeatureModel::factory()->create();
Livewire::actingAs($userWithoutPermission)->test(PerfumeFeature::class)
->call('delete', $perfumeFeature)
->assertForbidden();
});
it('requires update perfume feature permission for sorting', function () {
// Create a user without Owner role for this test
$userWithoutPermission = User::factory()->active()->create();
$item1 = PerfumeFeatureModel::create(['name' => 'Item 1', 'description' => 'Desc 1', 'sort_order' => 1]);
$item2 = PerfumeFeatureModel::create(['name' => 'Item 2', 'description' => 'Desc 2', 'sort_order' => 2]);
Livewire::actingAs($userWithoutPermission)->test(PerfumeFeature::class)
->call('sortOrder', $item2, 'up')
->assertForbidden();
});