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 data $this->outlets = Outlet::factory()->count(3)->create(); $this->brands = Brand::factory()->count(2)->create(); $this->categories = Category::factory()->count(3)->create(); }); function mountPerfumeEditComponent(User $user, Perfume $perfume) { return Livewire::actingAs($user)->test(\App\Livewire\Studio\Catalog\Perfume\Edit::class, ['perfume' => $perfume->hash]); } /* |-------------------------------------------------------------------------- | Edit Component Rendering & Mount |-------------------------------------------------------------------------- */ it('renders perfume edit page successfully', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->assertViewIs('livewire.studio.catalog.perfume.form') ->assertViewHas('pageTitle', 'Ubah Parfum'); }); it('populates perfume form with perfume data on mount', function () { $brand = $this->brands->first(); $perfume = Perfume::factory()->create([ 'name' => 'Test Perfume', 'sku' => 'TP001', 'brand_id' => $brand->id, 'cost_price' => 150000, 'sale_price' => 250000, 'concentration' => Concentration::EAU_DE_PERFUME, 'base_notes' => 'Vanilla, Musk', 'middle_notes' => 'Jasmine, Rose', 'top_notes' => 'Bergamot, Lemon', 'description' => 'Test description', ]); $perfume->categories()->attach([$this->categories->first()->id]); $perfume->outlets()->attach([$this->outlets->first()->id]); $component = mountPerfumeEditComponent($this->user, $perfume); expect($component->form->name)->toBe('Test Perfume'); expect($component->form->sku)->toBe('TP001'); expect($component->form->brand_id)->toBe((string) $brand->id); expect($component->form->cost_price)->toBe('150000'); expect($component->form->sale_price)->toBe('250000'); expect($component->form->concentration)->toBe((string) Concentration::EAU_DE_PERFUME->value); expect($component->form->base_notes)->toBe('Vanilla, Musk'); expect($component->form->middle_notes)->toBe('Jasmine, Rose'); expect($component->form->top_notes)->toBe('Bergamot, Lemon'); expect($component->form->description)->toBe('Test description'); expect($component->form->category_ids)->toBe([$this->categories->first()->id]); expect($component->form->outlet_ids)->toBe([$this->outlets->first()->id]); }); /* |-------------------------------------------------------------------------- | Update Perfume Tests |-------------------------------------------------------------------------- */ it('updates perfume successfully when authorized', function () { $updatePermission = Permission::where('name', 'update perfume')->first(); $this->user->givePermissionTo($updatePermission); $oldBrand = $this->brands->first(); $perfume = Perfume::factory()->create([ 'name' => 'Original Perfume', 'sku' => 'OP001', 'brand_id' => $oldBrand->id, 'cost_price' => 100000, 'sale_price' => 200000, 'concentration' => Concentration::EAU_DE_TOILETTE, ]); $perfume->categories()->attach([$this->categories->first()->id]); $perfume->outlets()->attach([$this->outlets->first()->id]); $newBrand = $this->brands->last(); $updateData = [ 'name' => 'Updated Perfume', 'sku' => 'UP001', 'brand_id' => $newBrand->id, 'cost_price' => '150000', 'sale_price' => '300000', 'concentration' => Concentration::EAU_DE_PERFUME->value, 'base_notes' => 'Updated Base Notes', 'middle_notes' => 'Updated Middle Notes', 'top_notes' => 'Updated Top Notes', 'description' => 'Updated description', 'category_ids' => [$this->categories->first()->id, $this->categories->last()->id], 'outlet_ids' => [$this->outlets->first()->id, $this->outlets->last()->id], ]; mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', $updateData['name']) ->set('form.sku', $updateData['sku']) ->set('form.brand_id', $updateData['brand_id']) ->set('form.cost_price', $updateData['cost_price']) ->set('form.sale_price', $updateData['sale_price']) ->set('form.concentration', $updateData['concentration']) ->set('form.base_notes', $updateData['base_notes']) ->set('form.middle_notes', $updateData['middle_notes']) ->set('form.top_notes', $updateData['top_notes']) ->set('form.description', $updateData['description']) ->set('form.category_ids', $updateData['category_ids']) ->set('form.outlet_ids', $updateData['outlet_ids']) ->call('save') ->assertHasNoErrors() ->assertRedirect(route('studio.catalog.perfume.index')); $perfume->refresh(); expect($perfume->name)->toBe($updateData['name']); expect($perfume->sku)->toBe($updateData['sku']); expect($perfume->brand_id)->toBe($updateData['brand_id']); expect($perfume->cost_price)->toBe(150000); expect($perfume->sale_price)->toBe(300000); expect($perfume->point_per_ml)->toBe(300); // 300000 / 1000 expect($perfume->concentration->value)->toBe($updateData['concentration']); expect($perfume->base_notes)->toBe($updateData['base_notes']); expect($perfume->middle_notes)->toBe($updateData['middle_notes']); expect($perfume->top_notes)->toBe($updateData['top_notes']); expect($perfume->description)->toBe($updateData['description']); expect($perfume->categories)->toHaveCount(2); expect($perfume->outlets)->toHaveCount(2); }); it('prevents perfume update when unauthorized', function () { $this->ownerRole->revokePermissionTo('update perfume'); Gate::define('update perfume', fn () => false); $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', 'Updated Name') ->call('save') ->assertForbidden(); }); it('syncs perfume category and outlet relationships correctly on update', function () { $updatePermission = Permission::where('name', 'update perfume')->first(); $this->user->givePermissionTo($updatePermission); $perfume = Perfume::factory()->create(); $perfume->categories()->attach([$this->categories->first()->id]); $perfume->outlets()->attach([$this->outlets->first()->id]); // Change to different categories and outlets mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', 'Updated Perfume') ->set('form.sku', 'UP001') ->set('form.sale_price', '200000') ->set('form.category_ids', [$this->categories[1]->id, $this->categories[2]->id]) ->set('form.outlet_ids', [$this->outlets[1]->id, $this->outlets[2]->id]) ->call('save') ->assertHasNoErrors(); $perfume->refresh(); expect($perfume->categories)->toHaveCount(2); expect($perfume->outlets)->toHaveCount(2); expect($perfume->categories->pluck('id')->toArray())->toBe([$this->categories[1]->id, $this->categories[2]->id]); expect($perfume->outlets->pluck('id')->toArray())->toBe([$this->outlets[1]->id, $this->outlets[2]->id]); }); it('allows same sku on update of same perfume', function () { $updatePermission = Permission::where('name', 'update perfume')->first(); $this->user->givePermissionTo($updatePermission); $perfume = Perfume::factory()->create(['sku' => 'EXISTING001']); mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', 'Updated Perfume') ->set('form.sku', 'EXISTING001') // Same SKU as existing perfume ->set('form.sale_price', '200000') ->set('form.category_ids', [$this->categories->first()->id]) ->set('form.outlet_ids', [$this->outlets->first()->id]) ->call('save') ->assertHasNoErrors(); }); it('updates perfume brand to null', function () { $updatePermission = Permission::where('name', 'update perfume')->first(); $this->user->givePermissionTo($updatePermission); $perfume = Perfume::factory()->create(['brand_id' => $this->brands->first()->id]); mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', 'Perfume Without Brand') ->set('form.sku', 'PWB001') ->set('form.brand_id', null) // Remove brand ->set('form.sale_price', '150000') ->set('form.category_ids', [$this->categories->first()->id]) ->set('form.outlet_ids', [$this->outlets->first()->id]) ->call('save') ->assertHasNoErrors(); $perfume->refresh(); expect($perfume->brand_id)->toBeNull(); }); /* |-------------------------------------------------------------------------- | Form Validation Tests on Edit |-------------------------------------------------------------------------- */ it('validates required name field on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', '') ->call('save') ->assertHasErrors(['form.name']); }); it('validates name field max length on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', str_repeat('a', 51)) ->call('save') ->assertHasErrors(['form.name']); }); it('validates required sku field on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.sku', '') ->call('save') ->assertHasErrors(['form.sku']); }); it('validates sku field max length on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.sku', str_repeat('a', 21)) ->call('save') ->assertHasErrors(['form.sku']); }); it('validates sku uniqueness on perfume update with different perfume', function () { $perfume1 = Perfume::factory()->create(['sku' => 'SKU001']); $perfume2 = Perfume::factory()->create(['sku' => 'SKU002']); mountPerfumeEditComponent($this->user, $perfume1) ->set('form.sku', 'SKU002') // Try to use SKU from perfume2 ->call('save') ->assertHasErrors(['form.sku']); }); it('validates brand_id exists if provided on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.brand_id', 999) // Non-existent brand ->call('save') ->assertHasErrors(['form.brand_id']); }); it('validates required sale_price field on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.sale_price', '') ->call('save') ->assertHasErrors(['form.sale_price']); }); it('validates concentration is valid enum value on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.concentration', 'invalid') ->call('save') ->assertHasErrors(['form.concentration']); }); it('validates required category_ids field on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.category_ids', []) ->call('save') ->assertHasErrors(['form.category_ids']); }); it('validates category_ids contains valid category ids on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.category_ids', [999]) ->call('save') ->assertHasErrors(['form.category_ids.*']); }); it('validates required outlet_ids field on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.outlet_ids', []) ->call('save') ->assertHasErrors(['form.outlet_ids']); }); it('validates outlet_ids contains valid outlet ids on perfume update', function () { $perfume = Perfume::factory()->create(); mountPerfumeEditComponent($this->user, $perfume) ->set('form.outlet_ids', [999]) ->call('save') ->assertHasErrors(['form.outlet_ids.*']); }); /* |-------------------------------------------------------------------------- | Real-time Validation Tests on Edit |-------------------------------------------------------------------------- */ it('validates perfume name field in real-time on edit', function () { $perfume = Perfume::factory()->create(); $component = mountPerfumeEditComponent($this->user, $perfume); // Valid name $component->set('form.name', 'Valid Updated Perfume 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 perfume sku field in real-time on edit', function () { $perfume = Perfume::factory()->create(); $component = mountPerfumeEditComponent($this->user, $perfume); // Valid SKU $component->set('form.sku', 'VALID001') ->assertHasNoErrors(['form.sku']); // Empty SKU $component->set('form.sku', '') ->assertHasErrors(['form.sku']); // SKU too long $component->set('form.sku', str_repeat('a', 21)) ->assertHasErrors(['form.sku']); }); it('validates perfume price fields in real-time on edit', function () { $perfume = Perfume::factory()->create(); $component = mountPerfumeEditComponent($this->user, $perfume); // Valid sale price $component->set('form.sale_price', '300000') ->assertHasNoErrors(['form.sale_price']); // Empty sale price $component->set('form.sale_price', '') ->assertHasErrors(['form.sale_price']); // Negative sale price $component->set('form.sale_price', '-300000') ->assertHasErrors(['form.sale_price']); }); it('validates perfume concentration field in real-time on edit', function () { $perfume = Perfume::factory()->create(); $component = mountPerfumeEditComponent($this->user, $perfume); // Valid concentration $component->set('form.concentration', Concentration::EAU_DE_PERFUME->value) ->assertHasNoErrors(['form.concentration']); // Invalid concentration $component->set('form.concentration', 'invalid') ->assertHasErrors(['form.concentration']); }); /* |-------------------------------------------------------------------------- | Integration Tests |-------------------------------------------------------------------------- */ it('updates perfume and syncs category and outlet relationships', function () { $updatePermission = Permission::where('name', 'update perfume')->first(); $this->user->givePermissionTo($updatePermission); $perfume = Perfume::factory()->create(); $perfume->categories()->attach([$this->categories->first()->id]); $perfume->outlets()->attach([$this->outlets->first()->id]); $newCategories = [$this->categories[1]->id, $this->categories[2]->id]; $newOutlets = [$this->outlets[1]->id, $this->outlets[2]->id]; mountPerfumeEditComponent($this->user, $perfume) ->set('form.name', 'Updated Integration Perfume') ->set('form.sku', 'UIP001') ->set('form.sale_price', '400000') ->set('form.category_ids', $newCategories) ->set('form.outlet_ids', $newOutlets) ->call('save') ->assertHasNoErrors(); $perfume->refresh(); expect($perfume->name)->toBe('Updated Integration Perfume'); expect($perfume->categories)->toHaveCount(2); expect($perfume->outlets)->toHaveCount(2); expect($perfume->categories->pluck('id')->toArray())->toBe($newCategories); expect($perfume->outlets->pluck('id')->toArray())->toBe($newOutlets); }); /* |-------------------------------------------------------------------------- | Error Handling and Edge Cases |-------------------------------------------------------------------------- */ it('handles invalid perfume hash in edit component', function () { expect(fn () => mountPerfumeEditComponent($this->user, 'invalid-hash')) ->toThrow(\TypeError::class); }); it('validates perfume form data integrity during operations', function () { // Test that form data is properly reset between operations $perfume1 = Perfume::factory()->create(['name' => 'First Perfume']); $perfume2 = Perfume::factory()->create(['name' => 'Second Perfume']); $component = mountPerfumeEditComponent($this->user, $perfume1); // Open modal for first perfume expect($component->form->name)->toBe('First Perfume'); // Verify first perfume wasn't modified $perfume1->refresh(); expect($perfume1->name)->toBe('First Perfume'); });