parfum/tests/Feature/Livewire/Studio/Catalog/Bottle/EditTest.php
Yoga Pangestu 81a9753620 feat(perfume. bottle): implementasi testing
- kasih return type
- ubah array to collection di audit
- mengubah waktu refresh tabel menjadi 30 detik
2025-12-11 11:15:59 +07:00

346 lines
11 KiB
PHP

<?php
use App\Models\Bottle;
use App\Models\Employee;
use App\Models\Outlet;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
$this->user = User::factory()
->active()
->has(Employee::factory())
->create();
$this->ownerRole = Role::where('name', 'Owner')->first();
$this->user->roles()->attach($this->ownerRole->id);
// Create test outlets
$this->outlets = Outlet::factory()->count(3)->create();
});
function mountBottleEditComponent(User $user, Bottle $bottle)
{
return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Bottle\Edit::class, ['bottle' => $bottle->hash]);
}
/*
|--------------------------------------------------------------------------
| Edit Component Rendering & Mount
|--------------------------------------------------------------------------
*/
it('renders bottle edit page successfully', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->assertViewIs('livewire.studio.catalog.bottle.form')
->assertViewHas('pageTitle', 'Ubah Botol');
});
it('populates bottle form with bottle data on mount', function () {
$bottle = Bottle::factory()->create([
'name' => 'Test Bottle',
'size' => 100,
'cost_price' => 50000,
'sale_price' => 75000,
'description' => 'Test description',
]);
$bottle->outlets()->attach([$this->outlets->first()->id]);
$component = mountBottleEditComponent($this->user, $bottle);
expect($component->form->name)->toBe('Test Bottle');
expect($component->form->size)->toBe('100');
expect($component->form->cost_price)->toBe('50000');
expect($component->form->sale_price)->toBe('75000');
expect($component->form->description)->toBe('Test description');
expect($component->form->outlet_ids)->toBe([$this->outlets->first()->id]);
});
/*
|--------------------------------------------------------------------------
| Update Bottle Tests
|--------------------------------------------------------------------------
*/
it('updates bottle successfully when authorized', function () {
$updatePermission = Permission::where('name', 'update bottle')->first();
$this->user->givePermissionTo($updatePermission);
$bottle = Bottle::factory()->create([
'name' => 'Original Bottle',
'size' => 50,
'cost_price' => 25000,
'sale_price' => 50000,
]);
$bottle->outlets()->attach([$this->outlets->first()->id]);
$updateData = [
'name' => 'Updated Bottle',
'size' => '100',
'cost_price' => '40000',
'sale_price' => '60000',
'description' => 'Updated description',
'outlet_ids' => [$this->outlets->first()->id, $this->outlets->last()->id],
];
mountBottleEditComponent($this->user, $bottle)
->set('form.name', $updateData['name'])
->set('form.size', $updateData['size'])
->set('form.cost_price', $updateData['cost_price'])
->set('form.sale_price', $updateData['sale_price'])
->set('form.description', $updateData['description'])
->set('form.outlet_ids', $updateData['outlet_ids'])
->call('save')
->assertHasNoErrors()
->assertRedirect(route('studio.catalog.bottle.index'));
$bottle->refresh();
expect($bottle->name)->toBe($updateData['name']);
expect($bottle->size)->toBe(100);
expect($bottle->cost_price)->toBe(40000);
expect($bottle->sale_price)->toBe(60000);
expect($bottle->point_per_pcs)->toBe(600); // 60000 / 100
expect($bottle->description)->toBe($updateData['description']);
expect($bottle->outlets)->toHaveCount(2);
});
it('prevents bottle update when unauthorized', function () {
$this->ownerRole->revokePermissionTo('update bottle');
Gate::define('update bottle', fn () => false);
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.name', 'Updated Name')
->call('save')
->assertForbidden();
});
it('syncs bottle outlet relationships correctly on update', function () {
$updatePermission = Permission::where('name', 'update bottle')->first();
$this->user->givePermissionTo($updatePermission);
$bottle = Bottle::factory()->create();
$bottle->outlets()->attach([$this->outlets->first()->id]);
// Change to different outlets
mountBottleEditComponent($this->user, $bottle)
->set('form.name', 'Updated Bottle')
->set('form.size', '200')
->set('form.sale_price', '50000')
->set('form.outlet_ids', [$this->outlets[1]->id, $this->outlets[2]->id])
->call('save')
->assertHasNoErrors();
$bottle->refresh();
expect($bottle->outlets)->toHaveCount(2);
expect($bottle->outlets->pluck('id')->toArray())->toBe([$this->outlets[1]->id, $this->outlets[2]->id]);
});
/*
|--------------------------------------------------------------------------
| Form Validation Tests on Edit
|--------------------------------------------------------------------------
*/
it('validates required name field on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.name', '')
->call('save')
->assertHasErrors(['form.name']);
});
it('validates name field max length on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.name', str_repeat('a', 51))
->call('save')
->assertHasErrors(['form.name']);
});
it('validates required size field on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.size', '')
->call('save')
->assertHasErrors(['form.size']);
});
it('validates size is unsigned integer on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.size', '-100')
->call('save')
->assertHasErrors(['form.size']);
});
it('validates required sale_price field on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.sale_price', '')
->call('save')
->assertHasErrors(['form.sale_price']);
});
it('validates sale_price is unsigned integer on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.sale_price', '-50000')
->call('save')
->assertHasErrors(['form.sale_price']);
});
it('validates required outlet_ids field on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.outlet_ids', [])
->call('save')
->assertHasErrors(['form.outlet_ids']);
});
it('validates outlet_ids contains valid outlet ids on bottle update', function () {
$bottle = Bottle::factory()->create();
mountBottleEditComponent($this->user, $bottle)
->set('form.outlet_ids', [999])
->call('save')
->assertHasErrors(['form.outlet_ids.*']);
});
/*
|--------------------------------------------------------------------------
| Real-time Validation Tests on Edit
|--------------------------------------------------------------------------
*/
it('validates bottle name field in real-time on edit', function () {
$bottle = Bottle::factory()->create();
$component = mountBottleEditComponent($this->user, $bottle);
// Valid name
$component->set('form.name', 'Valid Updated Bottle Name')
->assertHasNoErrors(['form.name']);
// Empty name
$component->set('form.name', '')
->assertHasErrors(['form.name']);
// Name too long
$component->set('form.name', str_repeat('a', 51))
->assertHasErrors(['form.name']);
});
it('validates bottle size field in real-time on edit', function () {
$bottle = Bottle::factory()->create();
$component = mountBottleEditComponent($this->user, $bottle);
// Valid size
$component->set('form.size', '150')
->assertHasNoErrors(['form.size']);
// Empty size
$component->set('form.size', '')
->assertHasErrors(['form.size']);
// Negative size
$component->set('form.size', '-50')
->assertHasErrors(['form.size']);
});
it('validates bottle price fields in real-time on edit', function () {
$bottle = Bottle::factory()->create();
$component = mountBottleEditComponent($this->user, $bottle);
// Valid sale price
$component->set('form.sale_price', '75000')
->assertHasNoErrors(['form.sale_price']);
// Empty sale price
$component->set('form.sale_price', '')
->assertHasErrors(['form.sale_price']);
// Negative sale price
$component->set('form.sale_price', '-75000')
->assertHasErrors(['form.sale_price']);
});
/*
|--------------------------------------------------------------------------
| Integration Tests
|--------------------------------------------------------------------------
*/
it('updates bottle and syncs outlet relationships', function () {
$updatePermission = Permission::where('name', 'update bottle')->first();
$this->user->givePermissionTo($updatePermission);
$bottle = Bottle::factory()->create();
$bottle->outlets()->attach([$this->outlets->first()->id]);
$newOutlets = [$this->outlets[1]->id, $this->outlets[2]->id];
mountBottleEditComponent($this->user, $bottle)
->set('form.name', 'Updated Integration Bottle')
->set('form.size', '300')
->set('form.sale_price', '90000')
->set('form.outlet_ids', $newOutlets)
->call('save')
->assertHasNoErrors();
$bottle->refresh();
expect($bottle->name)->toBe('Updated Integration Bottle');
expect($bottle->size)->toBe(300);
expect($bottle->outlets)->toHaveCount(2);
expect($bottle->outlets->pluck('id')->toArray())->toBe($newOutlets);
});
/*
|--------------------------------------------------------------------------
| Error Handling and Edge Cases
|--------------------------------------------------------------------------
*/
it('handles invalid bottle hash in edit component', function () {
expect(fn () => mountBottleEditComponent($this->user, 'invalid-hash'))
->toThrow(\TypeError::class);
});
it('validates bottle form data integrity during operations', function () {
// Test that form data is properly reset between operations
$bottle1 = Bottle::factory()->create(['name' => 'First Bottle']);
$bottle2 = Bottle::factory()->create(['name' => 'Second Bottle']);
$component = mountBottleEditComponent($this->user, $bottle1);
// Open modal for first bottle
expect($component->form->name)->toBe('First Bottle');
// Verify first bottle wasn't modified
$bottle1->refresh();
expect($bottle1->name)->toBe('First Bottle');
});