From 84cd57e5dca5877e46ae967d76e92dc9f797b055 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 17 Mar 2026 22:54:46 +0700 Subject: [PATCH] test: Add feature tests for Location and SubLocation resources to validate CRUD operations and permission handling. --- .../Master/Locations/LocationResourceTest.php | 214 +++++++++++++++++ .../SubLocations/SubLocationResourceTest.php | 218 ++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 tests/Feature/Filament/Resources/Master/Locations/LocationResourceTest.php create mode 100644 tests/Feature/Filament/Resources/Master/SubLocations/SubLocationResourceTest.php diff --git a/tests/Feature/Filament/Resources/Master/Locations/LocationResourceTest.php b/tests/Feature/Filament/Resources/Master/Locations/LocationResourceTest.php new file mode 100644 index 0000000..1ee8327 --- /dev/null +++ b/tests/Feature/Filament/Resources/Master/Locations/LocationResourceTest.php @@ -0,0 +1,214 @@ +user = User::factory()->create(['is_active' => IsActive::ACTIVE]); + + // Setup Permissions + Permission::firstOrCreate(['name' => 'ViewAny:Location', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Create:Location', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Update:Location', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Delete:Location', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Restore:Location', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'ForceDelete:Location', 'guard_name' => 'web']); + + $this->user->givePermissionTo([ + 'ViewAny:Location', + 'Create:Location', + 'Update:Location', + 'Delete:Location', + 'Restore:Location', + 'ForceDelete:Location', + ]); +}); + +/* +|-------------------------------------------------------------------------- +| Positive Scenarios +|-------------------------------------------------------------------------- +*/ + +test('can render location list page', function () { + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->assertSuccessful(); +}); + +test('can list locations', function () { + $locations = Location::factory()->count(5)->create(); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->call('loadTable') + ->assertCanSeeTableRecords($locations); +}); + +test('can create location', function () { + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->mountAction('create') + ->setActionData([ + 'name' => 'Lokus Baru', + ]) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $this->assertDatabaseHas('locations', [ + 'name' => 'Lokus Baru', + 'is_active' => IsActive::ACTIVE->value, + ]); +}); + +test('can edit location', function () { + $location = Location::factory()->create(); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->mountTableAction('edit', $location) + ->setActionData([ + 'name' => 'Lokus Updated', + ]) + ->callMountedTableAction() + ->assertHasNoActionErrors(); + + expect($location->refresh()->name)->toBe('Lokus Updated'); +}); + +test('can toggle is_active status', function () { + $location = Location::factory()->create(['is_active' => IsActive::INACTIVE]); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->call('updateTableColumnState', 'is_active', $location->getKey(), true); + + expect($location->refresh()->is_active)->toBe(IsActive::ACTIVE); +}); + +/* +|-------------------------------------------------------------------------- +| Negative Scenarios (Validation & Authorization) +|-------------------------------------------------------------------------- +*/ + +test('cannot create location with empty name', function () { + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->mountAction('create') + ->setActionData(['name' => '']) + ->callMountedAction() + ->assertHasActionErrors(['name' => 'required']); +}); + +test('cannot create location with name exceeding 50 characters', function () { + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->mountAction('create') + ->setActionData(['name' => str_repeat('a', 51)]) + ->callMountedAction() + ->assertHasActionErrors(['name' => 'max']); +}); + +test('unauthorized user cannot render location page', function () { + $unauthorizedUser = User::factory()->create(['is_active' => IsActive::ACTIVE]); + + $this->actingAs($unauthorizedUser); + + Livewire::test(ManageLocations::class) + ->assertForbidden(); +}); + +test('user without create permission cannot see create action', function () { + $viewOnlyUser = User::factory()->create(['is_active' => IsActive::ACTIVE]); + $viewOnlyUser->givePermissionTo('ViewAny:Location'); + $location = Location::factory()->create(); + + $this->actingAs($viewOnlyUser); + + Livewire::test(ManageLocations::class) + ->assertActionHidden('create') + ->assertTableActionHidden('edit', $location); +}); + +/* +|-------------------------------------------------------------------------- +| Soft Delete Scenarios +|-------------------------------------------------------------------------- +*/ + +test('can delete location', function () { + $location = Location::factory()->create(); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->callTableAction('delete', $location); + + $this->assertSoftDeleted($location); +}); + +test('can restore deleted location', function () { + $roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']); + $this->user->assignRole($roleDev); + + $location = Location::factory()->create(['deleted_at' => now()]); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->filterTable('trashed', 'with') + ->callTableAction('restore', $location); + + $this->assertDatabaseHas('locations', [ + 'id' => $location->id, + 'deleted_at' => null, + ]); +}); + +test('can force delete location', function () { + $roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']); + $this->user->assignRole($roleDev); + + $location = Location::factory()->create(['deleted_at' => now()]); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->filterTable('trashed', 'only') + ->callTableAction('forceDelete', $location); + + $this->assertDatabaseMissing('locations', ['id' => $location->id]); +}); + +test('trashed filter visibility depends on developer role', function () { + $roleAdmin = Role::firstOrCreate(['name' => RoleEnum::ADMINISTRATOR->value, 'guard_name' => 'web']); + $this->user->assignRole($roleAdmin); + + $this->actingAs($this->user); + + Livewire::test(ManageLocations::class) + ->assertTableFilterHidden('trashed'); + + $roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']); + $this->user->assignRole($roleDev); + + Livewire::test(ManageLocations::class) + ->assertTableFilterVisible('trashed'); +}); diff --git a/tests/Feature/Filament/Resources/Master/SubLocations/SubLocationResourceTest.php b/tests/Feature/Filament/Resources/Master/SubLocations/SubLocationResourceTest.php new file mode 100644 index 0000000..648a75f --- /dev/null +++ b/tests/Feature/Filament/Resources/Master/SubLocations/SubLocationResourceTest.php @@ -0,0 +1,218 @@ +user = User::factory()->create(['is_active' => IsActive::ACTIVE]); + + // Setup Permissions + Permission::firstOrCreate(['name' => 'ViewAny:SubLocation', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Create:SubLocation', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Update:SubLocation', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Delete:SubLocation', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'Restore:SubLocation', 'guard_name' => 'web']); + Permission::firstOrCreate(['name' => 'ForceDelete:SubLocation', 'guard_name' => 'web']); + + $this->user->givePermissionTo([ + 'ViewAny:SubLocation', + 'Create:SubLocation', + 'Update:SubLocation', + 'Delete:SubLocation', + 'Restore:SubLocation', + 'ForceDelete:SubLocation', + ]); +}); + +/* +|-------------------------------------------------------------------------- +| Positive Scenarios +|-------------------------------------------------------------------------- +*/ + +test('can render sub-location list page', function () { + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->assertSuccessful(); +}); + +test('can list sub-locations', function () { + $subLocations = SubLocation::factory()->count(5)->create(); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->call('loadTable') + ->assertCanSeeTableRecords($subLocations); +}); + +test('can create sub-location', function () { + $location = Location::factory()->create(['is_active' => IsActive::ACTIVE]); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->mountAction('create') + ->setActionData([ + 'location_id' => $location->id, + 'name' => 'Sub Lokus Baru', + ]) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $this->assertDatabaseHas('sub_locations', [ + 'location_id' => $location->id, + 'name' => 'Sub Lokus Baru', + ]); +}); + +test('can edit sub-location', function () { + $subLocation = SubLocation::factory()->create(); + $newLocation = Location::factory()->create(['is_active' => IsActive::ACTIVE]); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->mountTableAction('edit', $subLocation) + ->setActionData([ + 'location_id' => $newLocation->id, + 'name' => 'Sub Lokus Updated', + ]) + ->callMountedTableAction() + ->assertHasNoActionErrors(); + + expect($subLocation->refresh()->name)->toBe('Sub Lokus Updated'); + expect($subLocation->location_id)->toBe($newLocation->id); +}); + +test('can toggle is_active status', function () { + $subLocation = SubLocation::factory()->create(['is_active' => IsActive::INACTIVE]); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->call('updateTableColumnState', 'is_active', $subLocation->getKey(), true); + + expect($subLocation->refresh()->is_active)->toBe(IsActive::ACTIVE); +}); + +/* +|-------------------------------------------------------------------------- +| Negative Scenarios (Validation & Authorization) +|-------------------------------------------------------------------------- +*/ + +test('cannot create sub-location with invalid data', function () { + $this->actingAs($this->user); + + // Empty name + Livewire::test(ManageSubLocations::class) + ->mountAction('create') + ->setActionData(['name' => '', 'location_id' => null]) + ->callMountedAction() + ->assertHasActionErrors(['name' => 'required', 'location_id' => 'required']); + + // Non-existent location + Livewire::test(ManageSubLocations::class) + ->mountAction('create') + ->setActionData(['name' => 'Valid Name', 'location_id' => 999999]) + ->callMountedAction() + ->assertHasActionErrors(['location_id']); +}); + +test('unauthorized user cannot render sub-location page', function () { + $unauthorizedUser = User::factory()->create(['is_active' => IsActive::ACTIVE]); + + $this->actingAs($unauthorizedUser); + + Livewire::test(ManageSubLocations::class) + ->assertForbidden(); +}); + +test('user without update permission cannot see edit action', function () { + $viewOnlyUser = User::factory()->create(['is_active' => IsActive::ACTIVE]); + $viewOnlyUser->givePermissionTo('ViewAny:SubLocation'); + $subLocation = SubLocation::factory()->create(); + + $this->actingAs($viewOnlyUser); + + Livewire::test(ManageSubLocations::class) + ->assertTableActionHidden('edit', $subLocation); +}); + +/* +|-------------------------------------------------------------------------- +| Soft Delete Scenarios +|-------------------------------------------------------------------------- +*/ + +test('can delete sub-location', function () { + $subLocation = SubLocation::factory()->create(); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->callTableAction('delete', $subLocation); + + $this->assertSoftDeleted($subLocation); +}); + +test('can restore deleted sub-location', function () { + $roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']); + $this->user->assignRole($roleDev); + + $subLocation = SubLocation::factory()->create(['deleted_at' => now()]); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->filterTable('trashed', 'with') + ->callTableAction('restore', $subLocation); + + $this->assertDatabaseHas('sub_locations', [ + 'id' => $subLocation->id, + 'deleted_at' => null, + ]); +}); + +test('can force delete sub-location', function () { + $roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']); + $this->user->assignRole($roleDev); + + $subLocation = SubLocation::factory()->create(['deleted_at' => now()]); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->filterTable('trashed', 'only') + ->callTableAction('forceDelete', $subLocation); + + $this->assertDatabaseMissing('sub_locations', ['id' => $subLocation->id]); +}); + +test('trashed filter visibility depends on developer role', function () { + $roleAdmin = Role::firstOrCreate(['name' => RoleEnum::ADMINISTRATOR->value, 'guard_name' => 'web']); + $this->user->assignRole($roleAdmin); + + $this->actingAs($this->user); + + Livewire::test(ManageSubLocations::class) + ->assertTableFilterHidden('trashed'); + + $roleDev = Role::firstOrCreate(['name' => RoleEnum::DEVELOPER->value, 'guard_name' => 'web']); + $this->user->assignRole($roleDev); + + Livewire::test(ManageSubLocations::class) + ->assertTableFilterVisible('trashed'); +});