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'); });