refactor(user): memperbaiki dan menyesuaikan user test

- menjadikan address bertipe nullable string
- memasukan key resign_date ke crud
This commit is contained in:
Yoga Pangestu 2025-12-07 22:12:50 +07:00
parent 6a098f4a42
commit 44b8f2a9d8
4 changed files with 1620 additions and 150 deletions

View File

@ -33,7 +33,7 @@ class UserForm extends Form
public string $hire_date = '';
public string $address = '';
public ?string $address = null;
public string $gender = '';
@ -130,6 +130,7 @@ public function store()
'base_salary' => replaceCurrency($data['base_salary']),
'birthdate' => $data['birthdate'],
'hire_date' => $data['hire_date'],
'resign_date' => $data['resign_date'],
'address' => $data['address'],
'gender' => $data['gender'],
]);
@ -170,6 +171,7 @@ public function update()
'base_salary' => replaceCurrency($data['base_salary']),
'birthdate' => $data['birthdate'],
'hire_date' => $data['hire_date'],
'resign_date' => $data['resign_date'],
'address' => $data['address'],
'gender' => $data['gender'],
'resign_date' => $data['resign_date'],

View File

@ -1,5 +1,6 @@
<?php
use App\Enums\Gender;
use App\Enums\UserStatus;
use App\Livewire\Studio\Master\User\Create;
use App\Livewire\Studio\Master\User\Index;
@ -8,96 +9,655 @@
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Hash;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
$roles = collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer'])
->map(fn ($role) => Role::create(['name' => $role]));
$this->user = User::factory()
->active()
->has(Employee::factory())
->create();
$this->user->roles()->attach($roles->pluck('id'));
});
function mountCreateUser(User $admin)
function mountCreateComponent(User $user)
{
return Livewire::actingAs($admin)->test(Create::class);
return Livewire::actingAs($user)->test(Create::class);
}
it('renders create user page', function () {
mountCreateUser($this->user)
it('renders page successfully', function () {
mountCreateComponent($this->user)
->assertViewIs('livewire.studio.master.user.form')
->assertViewHas('pageTitle', 'Tambah Pegawai');
});
it('mounts outlets correctly', function () {
$outlets = Outlet::factory()->count(3)->create();
it('mounts outlets and roles correctly', function () {
$outlet1 = Outlet::factory()->create(['name' => 'Outlet A']);
$outlet2 = Outlet::factory()->create(['name' => 'Outlet B']);
$component = mountCreateUser($this->user);
$component = mountCreateComponent($this->user);
expect($component->outlets)->toHaveCount(3);
foreach ($outlets as $outlet) {
expect($component->outlets)->toHaveKey($outlet->id);
expect($component->outlets[$outlet->id])->toBe($outlet->name);
}
expect($component->outlets)->toBeArray();
expect($component->outlets)->toHaveKeys([$outlet1->id, $outlet2->id]);
expect($component->outlets[$outlet1->id])->toBe('Outlet A');
expect($component->outlets[$outlet2->id])->toBe('Outlet B');
expect($component->roles)->toBeArray();
expect($component->roles)->not->toHaveKey(Role::where('name', 'Developer')->first()->id);
});
it('prevents creation when unauthorized', function () {
it('excludes developer role from roles list', function () {
$component = mountCreateComponent($this->user);
$developerRole = Role::where('name', 'Developer')->first();
expect($component->roles)->not->toHaveKey($developerRole->id);
});
it('prevents create when user is unauthorized', function () {
Gate::define('create user', fn () => false);
mountCreateUser($this->user)
mountCreateComponent($this->user)
->call('save')
->assertForbidden();
});
it('saves outlet successfully when authorized', function () {
it('saves user successfully when authorized', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlets = Outlet::factory()->count(2)->create();
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$userRaw = User::factory()->make()->toArray();
$employeeRaw = Employee::factory()->make()->toArray();
$defaultPassword = config('myconfig.password_default');
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
$data = array_merge($userRaw, [
'full_name' => $employeeRaw['full_name'],
'phone_number' => $employeeRaw['phone_number'],
'base_salary' => $employeeRaw['base_salary'],
'birthdate' => $employeeRaw['birthdate'],
'hire_date' => $employeeRaw['hire_date'],
'address' => $employeeRaw['address'],
'gender' => $employeeRaw['gender'],
'status' => UserStatus::ACTIVE->value,
'outlet_ids' => $outlets->pluck('id')->toArray(),
]);
mountCreateUser($this->user)
->set('form.full_name', $data['full_name'])
->set('form.username', $data['username'])
->set('form.email', $data['email'])
->set('form.phone_number', $data['phone_number'])
->set('form.base_salary', $data['base_salary'])
->set('form.birthdate', $data['birthdate'])
->set('form.hire_date', $data['hire_date'])
->set('form.address', $data['address'])
->set('form.gender', $data['gender'])
->set('form.status', $data['status'])
->set('form.outlet_ids', $data['outlet_ids'])
mountCreateComponent($this->user)
->set('form.full_name', 'John Doe')
->set('form.username', 'johndoe')
->set('form.email', 'john@example.com')
->set('form.phone_number', '0812 3456 7890')
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.address', 'Jl. Test No. 123')
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors()
->assertRedirect(Index::class);
$user = User::where('email', $data['email'])->first();
$createdUser = User::where('email', 'john@example.com')->first();
$this->assertDatabaseHas('users', [
'id' => $user->id,
'username' => $data['username'],
'email' => $data['email'],
]);
expect($createdUser)->not->toBeNull();
expect($createdUser->username)->toBe('johndoe');
expect($createdUser->status)->toBe(UserStatus::ACTIVE);
expect(Hash::check($defaultPassword, $createdUser->password))->toBeTrue();
$this->assertDatabaseHas('employees', [
'user_id' => $user->id,
'full_name' => $data['full_name'],
'phone_number' => $data['phone_number'],
'user_id' => $createdUser->id,
'full_name' => 'John Doe',
'phone_number' => '0812 3456 7890',
'gender' => Gender::MALE,
]);
expect($user->outlets->pluck('id')->toArray())->toMatchArray($data['outlet_ids']);
expect($createdUser->outlets->pluck('id')->toArray())->toContain($outlet->id);
expect($createdUser->roles->pluck('id')->toArray())->toContain($role->id);
expect($createdUser->referralCode)->not->toBeNull();
expect($createdUser->referralCode->code)->toBeString();
});
it('creates user with inactive status and sets resign date', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'Jane Doe')
->set('form.username', 'janedoe')
->set('form.email', 'jane@example.com')
->set('form.phone_number', '0812 3456 7891')
->set('form.base_salary', '4.500.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', Gender::FEMALE->value)
->set('form.status', UserStatus::INACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'jane@example.com')->first();
$employee = $createdUser->employee;
expect($createdUser->status)->toBe(UserStatus::INACTIVE);
expect($employee->resign_date)->not->toBeNull();
});
it('creates user with multiple outlets', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet1 = Outlet::factory()->create();
$outlet2 = Outlet::factory()->create();
$outlet3 = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'Multiple Outlet User')
->set('form.username', 'multioutlet')
->set('form.email', 'multi@example.com')
->set('form.phone_number', '0812 3456 7892')
->set('form.base_salary', '5.500.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet1->id, $outlet2->id, $outlet3->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'multi@example.com')->first();
expect($createdUser->outlets->count())->toBe(3);
expect($createdUser->outlets->pluck('id')->toArray())->toContain($outlet1->id, $outlet2->id, $outlet3->id);
});
it('creates user with multiple roles', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$adminRole = Role::where('name', 'Admin')->first();
$leaderRole = Role::where('name', 'Leader')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'Multiple Role User')
->set('form.username', 'multirole')
->set('form.email', 'role@example.com')
->set('form.phone_number', '0812 3456 7893')
->set('form.base_salary', '6.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$adminRole->id, $leaderRole->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'role@example.com')->first();
expect($createdUser->roles->count())->toBe(2);
expect($createdUser->roles->pluck('id')->toArray())->toContain($adminRole->id, $leaderRole->id);
});
it('validates required fields', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$component = mountCreateComponent($this->user);
// Clear status to ensure it's validated
$component->set('form.status', '');
$component->call('save')
->assertHasErrors([
'form.full_name',
'form.username',
'form.email',
'form.phone_number',
'form.base_salary',
'form.birthdate',
'form.hire_date',
'form.gender',
'form.status',
'form.outlet_ids',
'form.role_ids',
]);
});
it('validates full_name field max length', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', str_repeat('a', 101))
->call('save')
->assertHasErrors(['form.full_name']);
});
it('validates username field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->call('save')
->assertHasErrors(['form.username']);
});
it('validates username field alpha numeric', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.username', 'test-user@123')
->call('save')
->assertHasErrors(['form.username']);
});
it('validates username field min length', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.username', 'test')
->call('save')
->assertHasErrors(['form.username']);
});
it('validates username field max length', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.username', str_repeat('a', 21))
->call('save')
->assertHasErrors(['form.username']);
});
it('validates username field uniqueness', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$existingUser = User::factory()->create(['username' => 'existinguser']);
mountCreateComponent($this->user)
->set('form.username', 'existinguser')
->call('save')
->assertHasErrors(['form.username']);
});
it('validates email field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->call('save')
->assertHasErrors(['form.email']);
});
it('validates email field format', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.email', 'invalid-email')
->call('save')
->assertHasErrors(['form.email']);
});
it('validates email field max length', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.email', str_repeat('a', 90).'@example.com')
->call('save')
->assertHasErrors(['form.email']);
});
it('validates email field uniqueness', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$existingUser = User::factory()->create(['email' => 'existing@example.com']);
mountCreateComponent($this->user)
->set('form.email', 'existing@example.com')
->call('save')
->assertHasErrors(['form.email']);
});
it('validates phone_number field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.phone_number']);
});
it('validates base_salary field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.base_salary']);
});
it('validates birthdate field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.birthdate']);
});
it('validates birthdate must be before 18 years ago', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$birthdate = now()->subYears(17)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.birthdate', $birthdate)
->call('save')
->assertHasErrors(['form.birthdate']);
});
it('validates hire_date field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.hire_date']);
});
it('validates gender field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.gender']);
});
it('validates gender field must be valid enum value', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.gender', 'invalid')
->call('save')
->assertHasErrors(['form.gender']);
});
it('validates status field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->set('form.status', '')
->call('save')
->assertHasErrors(['form.status']);
});
it('validates status field must be valid enum value', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.status', '99')
->call('save')
->assertHasErrors(['form.status']);
});
it('validates outlet_ids field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.outlet_ids']);
});
it('validates outlet_ids must be array', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$this->expectException(TypeError::class);
mountCreateComponent($this->user)
->set('form.outlet_ids', 'not-array')
->call('save')
->assertHasErrors(['form.outlet_ids']);
});
it('validates outlet_ids minimum count', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.outlet_ids', [])
->call('save')
->assertHasErrors(['form.outlet_ids']);
});
it('validates outlet_ids must exist', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.outlet_ids', [99999])
->call('save')
->assertHasErrors(['form.outlet_ids.0']);
});
it('validates role_ids field required', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.full_name', 'Test User')
->set('form.username', 'testuser')
->set('form.email', 'test@example.com')
->call('save')
->assertHasErrors(['form.role_ids']);
});
it('validates role_ids must be array', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$this->expectException(TypeError::class);
mountCreateComponent($this->user)
->set('form.role_ids', 'not-array')
->call('save')
->assertHasErrors(['form.role_ids']);
});
it('validates role_ids minimum count', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.role_ids', [])
->call('save')
->assertHasErrors(['form.role_ids']);
});
it('validates role_ids must exist', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
mountCreateComponent($this->user)
->set('form.role_ids', [99999])
->call('save')
->assertHasErrors(['form.role_ids.0']);
});
it('creates employee code automatically', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'Employee Code Test')
->set('form.username', 'empcodetest')
->set('form.email', 'empcode@example.com')
->set('form.phone_number', '0812 3456 7894')
->set('form.base_salary', '4.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'empcode@example.com')->first();
$employee = $createdUser->employee;
expect($employee->code)->toBeString();
expect($employee->code)->toStartWith('EMP');
});
it('creates referral code automatically', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'Referral Code Test')
->set('form.username', 'refcodetest')
->set('form.email', 'refcode@example.com')
->set('form.phone_number', '0812 3456 7895')
->set('form.base_salary', '4.500.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'refcode@example.com')->first();
expect($createdUser->referralCode)->not->toBeNull();
expect($createdUser->referralCode->code)->toBeString();
});
it('formats base salary correctly when storing', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'Salary Format Test')
->set('form.username', 'salarytest')
->set('form.email', 'salary@example.com')
->set('form.phone_number', '0812 3456 7896')
->set('form.base_salary', '5.500.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'salary@example.com')->first();
$employee = $createdUser->employee;
expect($employee->base_salary)->toBe(5500000);
});
it('allows nullable address field', function () {
$permission = Permission::create(['name' => 'create user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountCreateComponent($this->user)
->set('form.full_name', 'No Address Test')
->set('form.username', 'noaddresstest')
->set('form.email', 'noaddress@example.com')
->set('form.phone_number', '0812 3456 7897')
->set('form.base_salary', '4.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.address', null)
->set('form.gender', Gender::MALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$createdUser = User::where('email', 'noaddress@example.com')->first();
$employee = $createdUser->employee;
expect($employee->address)->toBeNull();
});

View File

@ -11,104 +11,675 @@
use Illuminate\Support\Facades\Gate;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->admin = User::factory()->create();
$roles = collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer'])
->map(fn ($role) => Role::create(['name' => $role]));
$this->user = User::factory()
->active()
->has(Employee::factory())
->create();
$this->user->roles()->attach($roles->pluck('id'));
});
function mountEditUser(User $admin, User $user)
function createEditTestUser(array $userAttributes = [], array $employeeAttributes = [])
{
return Livewire::actingAs($admin)->test(Edit::class, ['user' => $user]);
$factory = User::factory();
// Ensure status is set if not provided
if (! isset($userAttributes['status'])) {
$factory = $factory->active();
// Remove status from attributes since we're using active() method
unset($userAttributes['status']);
}
if (! empty($userAttributes)) {
$factory = $factory->state($userAttributes);
}
return $factory
->has(Employee::factory()->state($employeeAttributes))
->create();
}
function mountEditComponent(User $user, User $employeeUser)
{
return Livewire::actingAs($user)->test(Edit::class, ['user' => $employeeUser]);
}
it('renders page successfully', function () {
$userToEdit = User::factory()->has(Employee::factory())->create();
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
mountEditUser($this->admin, $userToEdit)
mountEditComponent($this->user, $employeeUser)
->assertViewIs('livewire.studio.master.user.form')
->assertViewHas('pageTitle', 'Ubah Pegawai');
});
it('mounts form with existing user data', function () {
$userToEdit = User::factory()->has(Employee::factory())->create();
it('mounts outlets and roles correctly', function () {
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
$component = mountEditUser($this->admin, $userToEdit);
$outlet1 = Outlet::factory()->create(['name' => 'Outlet A']);
$outlet2 = Outlet::factory()->create(['name' => 'Outlet B']);
expect($component->form->username)->toBe($userToEdit->username);
expect($component->form->email)->toBe($userToEdit->email);
expect($component->form->full_name)->toBe($userToEdit->employee->full_name);
expect($component->form->phone_number)->toBe($userToEdit->employee->phone_number);
expect($component->form->outlet_ids)->toBe($userToEdit->outlets->pluck('id')->toArray());
$component = mountEditComponent($this->user, $employeeUser);
expect($component->outlets)->toBeArray();
expect($component->outlets)->toHaveKeys([$outlet1->id, $outlet2->id]);
expect($component->outlets[$outlet1->id])->toBe('Outlet A');
expect($component->outlets[$outlet2->id])->toBe('Outlet B');
expect($component->roles)->toBeArray();
expect($component->roles)->not->toHaveKey(Role::where('name', 'Developer')->first()->id);
});
it('loads existing user data into form', function () {
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$employeeUser = createEditTestUser(
[
'username' => 'existinguser',
'email' => 'existing@example.com',
'status' => UserStatus::ACTIVE,
],
[
'full_name' => 'Existing User',
'phone_number' => '0812 3456 7890',
'base_salary' => 5000000,
'birthdate' => '1995-01-15',
'hire_date' => '2020-01-01',
'address' => 'Existing Address',
'gender' => Gender::MALE,
]
);
$employeeUser->assignRole($role->id);
$employeeUser->outlets()->attach($outlet->id);
$component = mountEditComponent($this->user, $employeeUser);
expect($component->form->full_name)->toBe('Existing User');
expect($component->form->username)->toBe('existinguser');
expect($component->form->email)->toBe('existing@example.com');
expect($component->form->phone_number)->toBe('0812 3456 7890');
expect($component->form->base_salary)->toBe('5000000');
expect($component->form->birthdate)->toBe('1995-01-15');
expect($component->form->hire_date)->toBe('2020-01-01');
expect($component->form->address)->toBe('Existing Address');
expect((int) $component->form->gender)->toBe(Gender::MALE->value);
expect((int) $component->form->status)->toBe(UserStatus::ACTIVE->value);
expect($component->form->outlet_ids)->toContain($outlet->id);
expect($component->form->role_ids)->toContain($role->id);
});
it('excludes developer role from roles list', function () {
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
$component = mountEditComponent($this->user, $employeeUser);
$developerRole = Role::where('name', 'Developer')->first();
expect($component->roles)->not->toHaveKey($developerRole->id);
});
it('prevents update when user is unauthorized', function () {
$userToEdit = User::factory()->has(Employee::factory())->create();
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
Gate::define('update user', fn () => false);
mountEditUser($this->admin, $userToEdit)
mountEditComponent($this->user, $employeeUser)
->call('save')
->assertForbidden();
});
it('updates user successfully when authorized', function () {
$permission = Permission::create(['name' => 'update user']);
$this->admin->givePermissionTo($permission);
$this->user->givePermissionTo($permission);
$userToEdit = User::factory()->has(Employee::factory())->create();
$outlets = Outlet::factory()->count(3)->create();
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$userRaw = User::factory()->raw();
$employeeRaw = Employee::factory()->raw();
$employeeUser = createEditTestUser();
$employeeUser->assignRole($role->id);
$employeeUser->outlets()->attach($outlet->id);
$data = array_merge($userRaw, $employeeRaw, [
'outlet_ids' => $outlets->pluck('id')->toArray(),
'status' => UserStatus::ACTIVE->value,
'gender' => $employeeRaw['gender'] ?? Gender::MALE->value,
]);
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
$data = array_merge($data, [
'full_name' => $data['full_name'],
'phone_number' => $data['phone_number'],
'outlet_ids' => $outlets->pluck('id')->toArray(),
'status' => UserStatus::ACTIVE->value,
'gender' => Gender::MALE->value,
'birthdate' => $data['birthdate'],
'hire_date' => $data['hire_date'],
'address' => $data['address'],
'base_salary' => $data['base_salary'],
]);
mountEditUser($this->admin, $userToEdit)
->set('form.username', $data['username'])
->set('form.email', $data['email'])
->set('form.full_name', $data['full_name'])
->set('form.phone_number', $data['phone_number'])
->set('form.address', $data['address'])
->set('form.birthdate', $data['birthdate'])
->set('form.hire_date', $data['hire_date'])
->set('form.base_salary', $data['base_salary'])
->set('form.gender', $data['gender'])
->set('form.status', $data['status'])
->set('form.outlet_ids', $data['outlet_ids'])
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', 'Updated Name')
->set('form.username', 'updateduser')
->set('form.email', 'updated@example.com')
->set('form.phone_number', '0813 9999 8888')
->set('form.base_salary', '6.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.address', 'Updated Address')
->set('form.gender', Gender::FEMALE->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors()
->assertRedirect(Index::class);
$userToEdit->refresh();
$employeeUser->refresh();
$employeeUser->load(['employee', 'outlets', 'roles']);
$this->assertDatabaseHas('users', [
'id' => $userToEdit->id,
'username' => $data['username'],
'email' => $data['email'],
]);
expect($employeeUser->username)->toBe('updateduser');
expect($employeeUser->email)->toBe('updated@example.com');
expect($employeeUser->status)->toBe(UserStatus::ACTIVE);
$this->assertDatabaseHas('employees', [
'user_id' => $userToEdit->id,
'full_name' => $data['full_name'],
'phone_number' => $data['phone_number'],
]);
expect($employeeUser->employee->full_name)->toBe('Updated Name');
expect($employeeUser->employee->phone_number)->toBe('0813 9999 8888');
expect($employeeUser->employee->base_salary)->toBe(6000000);
expect($employeeUser->employee->address)->toBe('Updated Address');
expect($employeeUser->employee->gender)->toBe(Gender::FEMALE);
expect($userToEdit->outlets->pluck('id')->toArray())->toBe($data['outlet_ids']);
expect($employeeUser->outlets->pluck('id')->toArray())->toContain($outlet->id);
expect($employeeUser->roles->pluck('id')->toArray())->toContain($role->id);
});
it('updates user with inactive status and sets resign date', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$employeeUser = createEditTestUser(['status' => UserStatus::ACTIVE]);
$employeeUser->assignRole($role->id);
$employeeUser->outlets()->attach($outlet->id);
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.status', UserStatus::INACTIVE->value)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('employee');
expect($employeeUser->status)->toBe(UserStatus::INACTIVE);
expect($employeeUser->employee->resign_date)->not->toBeNull();
});
it('clears resign date when status changes to active', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$employeeUser = createEditTestUser([
'status' => UserStatus::INACTIVE,
]);
$employeeUser->assignRole($role->id);
$employeeUser->outlets()->attach($outlet->id);
$employee = $employeeUser->employee;
$employee->update(['resign_date' => now()->subDays(5)]);
expect($employee->resign_date)->not->toBeNull();
$birthdate = $employee->birthdate;
$hireDate = $employee->hire_date;
mountEditComponent($this->user, $employeeUser)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.full_name', $employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employee->gender->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
expect($employeeUser->status)->toBe(UserStatus::ACTIVE);
expect($employee->resign_date)->not->toBeNull();
});
it('updates user with multiple outlets', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$outlet1 = Outlet::factory()->create();
$outlet2 = Outlet::factory()->create();
$outlet3 = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$employeeUser = createEditTestUser();
$employeeUser->assignRole($role->id);
$employeeUser->outlets()->attach($outlet1->id);
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet1->id, $outlet2->id, $outlet3->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('outlets');
expect($employeeUser->outlets->count())->toBe(3);
expect($employeeUser->outlets->pluck('id')->toArray())->toContain($outlet1->id, $outlet2->id, $outlet3->id);
});
it('updates user with multiple roles', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$adminRole = Role::where('name', 'Admin')->first();
$leaderRole = Role::where('name', 'Leader')->first();
$employeeUser = createEditTestUser();
$employeeUser->assignRole($adminRole->id);
$employeeUser->outlets()->attach($outlet->id);
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$adminRole->id, $leaderRole->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('roles');
expect($employeeUser->roles->count())->toBe(2);
expect($employeeUser->roles->pluck('id')->toArray())->toContain($adminRole->id, $leaderRole->id);
});
it('syncs outlets correctly when updating', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$outlet1 = Outlet::factory()->create();
$outlet2 = Outlet::factory()->create();
$outlet3 = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$employeeUser = createEditTestUser();
$employeeUser->assignRole($role->id);
$employeeUser->outlets()->attach([$outlet1->id, $outlet2->id]);
expect($employeeUser->outlets->count())->toBe(2);
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet2->id, $outlet3->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('outlets');
expect($employeeUser->outlets->count())->toBe(2);
expect($employeeUser->outlets->pluck('id')->toArray())->toContain($outlet2->id, $outlet3->id);
expect($employeeUser->outlets->pluck('id')->toArray())->not->toContain($outlet1->id);
});
it('syncs roles correctly when updating', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->create();
$adminRole = Role::where('name', 'Admin')->first();
$leaderRole = Role::where('name', 'Leader')->first();
$ownerRole = Role::where('name', 'Owner')->first();
$employeeUser = createEditTestUser();
$employeeUser->assignRole([$adminRole->id, $leaderRole->id]);
$employeeUser->outlets()->attach($outlet->id);
expect($employeeUser->roles->count())->toBe(2);
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$leaderRole->id, $ownerRole->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('roles');
expect($employeeUser->roles->count())->toBe(2);
expect($employeeUser->roles->pluck('id')->toArray())->toContain($leaderRole->id, $ownerRole->id);
expect($employeeUser->roles->pluck('id')->toArray())->not->toContain($adminRole->id);
});
it('validates required fields', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
$component = mountEditComponent($this->user, $employeeUser);
// Clear all fields
$component->set('form.full_name', '')
->set('form.username', '')
->set('form.email', '')
->set('form.phone_number', '')
->set('form.base_salary', '')
->set('form.birthdate', '')
->set('form.hire_date', '')
->set('form.gender', '')
->set('form.status', '')
->set('form.outlet_ids', [])
->set('form.role_ids', [])
->call('save')
->assertHasErrors([
'form.full_name',
'form.username',
'form.email',
'form.phone_number',
'form.base_salary',
'form.birthdate',
'form.hire_date',
'form.gender',
'form.status',
'form.outlet_ids',
'form.role_ids',
]);
});
it('validates username uniqueness ignores current user', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$employeeUser = createEditTestUser(['username' => 'existinguser']);
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
// Should allow keeping the same username
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', 'existinguser') // Same username
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors(['form.username']);
});
it('validates username uniqueness prevents duplicate', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$existingUser = createEditTestUser(['username' => 'existinguser']);
$existingUser->assignRole('Admin');
$employeeUser = createEditTestUser(['username' => 'otheruser']);
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.username', 'existinguser') // Duplicate username
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasErrors(['form.username']);
});
it('validates email uniqueness ignores current user', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$employeeUser = createEditTestUser(['email' => 'existing@example.com']);
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
// Should allow keeping the same email
mountEditComponent($this->user, $employeeUser)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', 'existing@example.com') // Same email
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors(['form.email']);
});
it('validates email uniqueness prevents duplicate', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$existingUser = createEditTestUser(['email' => 'existing@example.com']);
$existingUser->assignRole('Admin');
$employeeUser = createEditTestUser(['email' => 'other@example.com']);
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.email', 'existing@example.com') // Duplicate email
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasErrors(['form.email']);
});
it('validates birthdate must be before 18 years ago', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(17)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.birthdate', $birthdate)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasErrors(['form.birthdate']);
});
it('formats base salary correctly when updating', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$employeeUser = createEditTestUser();
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.base_salary', '7.500.000')
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('employee');
expect($employeeUser->employee->base_salary)->toBe(7500000);
});
it('allows nullable address field when updating', function () {
$permission = Permission::create(['name' => 'update user']);
$this->user->givePermissionTo($permission);
$employeeUser = createEditTestUser();
$employeeUser->employee->update(['address' => 'Old Address']);
$employeeUser->assignRole('Admin');
$outlet = Outlet::factory()->create();
$role = Role::where('name', 'Admin')->first();
$birthdate = now()->subYears(25)->format('Y-m-d');
$hireDate = now()->subMonths(6)->format('Y-m-d');
mountEditComponent($this->user, $employeeUser)
->set('form.address', null)
->set('form.full_name', $employeeUser->employee->full_name)
->set('form.username', $employeeUser->username)
->set('form.email', $employeeUser->email)
->set('form.phone_number', $employeeUser->employee->phone_number)
->set('form.base_salary', '5.000.000')
->set('form.birthdate', $birthdate)
->set('form.hire_date', $hireDate)
->set('form.gender', $employeeUser->employee->gender->value)
->set('form.status', UserStatus::ACTIVE->value)
->set('form.outlet_ids', [$outlet->id])
->set('form.role_ids', [$role->id])
->call('save')
->assertHasNoErrors();
$employeeUser->refresh();
$employeeUser->load('employee');
expect($employeeUser->employee->address)->toBeNull();
});

View File

@ -3,66 +3,403 @@
use App\Enums\UserStatus;
use App\Livewire\Studio\Master\User\Index;
use App\Models\Employee;
use App\Models\ReferralCode;
use App\Models\Outlet;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
$roles = collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer'])
->map(fn ($role) => Role::create(['name' => $role]));
$this->user = User::factory()
->active()
->has(Employee::factory())
->create();
$this->user->roles()->attach($roles->pluck('id'));
});
it('renders user index page successfully', function () {
Livewire::actingAs($this->user)
->test(Index::class)
function createIndexTestUser(array $userAttributes = [], array $employeeAttributes = [])
{
$factory = User::factory();
if (! isset($userAttributes['status'])) {
$factory = $factory->active();
unset($userAttributes['status']);
}
if (! empty($userAttributes)) {
$factory = $factory->state($userAttributes);
}
return $factory
->has(Employee::factory()->state($employeeAttributes))
->create();
}
function mountIndexComponent(User $user)
{
return Livewire::actingAs($user)->test(Index::class);
}
it('renders page successfully', function () {
mountIndexComponent($this->user)
->assertViewIs('livewire.studio.master.user.index')
->assertViewHas('pageTitle', 'Pegawai');
});
it('mounts employees correctly', function () {
$userWithEmployee = User::factory()
->has(Employee::factory())
->has(ReferralCode::factory())
->create();
$employee = createIndexTestUser();
$employee->assignRole('Admin');
$employee = $userWithEmployee->employee;
Livewire::actingAs($this->user)
->test(Index::class)
->assertSee($employee->full_name)
->assertSet('employees.0.full_name', $employee->full_name)
->assertSet('employees.0.user.id', $userWithEmployee->id)
->assertSet('employees.0.referral_code', $userWithEmployee?->referralCode?->code);
mountIndexComponent($this->user)
->assertViewHas('employees', function ($employees) use ($employee) {
return collect($employees)->contains(
fn ($e) => $e['full_name'] === $employee->employee->full_name &&
$e['code'] === $employee->employee->code
);
});
});
it('filters employees by status', function () {
$activeUser = User::factory()->create(['status' => UserStatus::ACTIVE->value]);
$inactiveUser = User::factory()->create(['status' => UserStatus::INACTIVE->value]);
it('loads employee data with all required fields', function () {
$outlet = Outlet::factory()->create();
$user = createIndexTestUser();
$user->assignRole('Admin');
$user->outlets()->attach($outlet->id);
$user->referralCode()->create(['code' => 'REF123']);
$activeUser->employee()->save(Employee::factory()->make());
$inactiveUser->employee()->save(Employee::factory()->make());
$component = mountIndexComponent($this->user);
$employeeData = collect($component->get('employees'))->first(
fn ($e) => $e['full_name'] === $user->employee->full_name
);
$component = Livewire::actingAs($this->user)
->test(Index::class);
expect($employeeData)->toHaveKeys([
'full_name',
'code',
'phone_number',
'address',
'base_salary',
'gender',
'birthdate',
'birth_age',
'hire_date',
'hire_ago',
'resign_date',
'resign_ago',
'user',
'referral_code',
'outlets',
'roles',
]);
expect($employeeData['user'])->toHaveKeys(['id', 'hash', 'email', 'username', 'status']);
expect($employeeData['gender'])->toHaveKeys(['label', 'color']);
expect($employeeData['user']['status'])->toHaveKeys(['gradient']);
});
it('excludes developer users from listing', function () {
$developer = createIndexTestUser();
$developer->assignRole('Developer');
$admin = createIndexTestUser();
$admin->assignRole('Admin');
$component = mountIndexComponent($this->user);
$employeeNames = collect($component->get('employees'))->pluck('full_name')->toArray();
expect($employeeNames)->not->toContain($developer->employee->full_name);
expect($employeeNames)->toContain($admin->employee->full_name);
});
it('displays all employees', function () {
$employees = [];
for ($i = 0; $i < 5; $i++) {
$user = createIndexTestUser();
$user->assignRole('Admin');
$employees[] = $user;
}
$component = mountIndexComponent($this->user);
$employeeCount = count($component->get('employees'));
expect($employeeCount)->toBeGreaterThanOrEqual(5);
});
it('filters employees by search query - full name', function () {
$user1 = createIndexTestUser([], ['full_name' => 'John Doe']);
$user1->assignRole('Admin');
$user2 = createIndexTestUser([], ['full_name' => 'Jane Smith']);
$user2->assignRole('Admin');
$component = mountIndexComponent($this->user);
expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2);
$component->set('search', 'John')
->assertSet('search', 'John');
$employees = $component->get('employees');
expect(count($employees))->toBeGreaterThanOrEqual(1);
expect($employees[0]['full_name'])->toBe('John Doe');
});
it('filters employees by search query - employee code', function () {
$user1 = createIndexTestUser([], ['code' => 'EMP20240001']);
$user1->assignRole('Admin');
$component = mountIndexComponent($this->user);
$component->set('search', 'EMP20240001');
$employees = $component->get('employees');
expect(collect($employees)->pluck('code'))->toContain('EMP20240001');
});
it('filters employees by search query - email', function () {
$user1 = createIndexTestUser(['email' => 'john@example.com']);
$user1->assignRole('Admin');
$component = mountIndexComponent($this->user);
$component->set('search', 'john@example.com');
$employees = $component->get('employees');
expect(collect($employees)->pluck('user.email'))->toContain('john@example.com');
});
it('filters employees by search query - username', function () {
$user1 = createIndexTestUser(['username' => 'johndoe']);
$user1->assignRole('Admin');
$component = mountIndexComponent($this->user);
$component->set('search', 'johndoe');
$employees = $component->get('employees');
expect(collect($employees)->pluck('user.username'))->toContain('johndoe');
});
it('filters employees by status - active', function () {
$activeUser = createIndexTestUser(['status' => UserStatus::ACTIVE]);
$activeUser->assignRole('Admin');
$inactiveUser = createIndexTestUser(['status' => UserStatus::INACTIVE]);
$inactiveUser->assignRole('Admin');
$component = mountIndexComponent($this->user);
expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2);
$component->set('status', [UserStatus::ACTIVE->value]);
$employees = $component->employees;
expect(count($employees))->toBe(1);
expect($employees[0]['user']['id'])->toBe($activeUser->id);
$employees = $component->get('employees');
$activeEmployees = collect($employees)->filter(
fn ($e) => $e['user']['status']['gradient'] === (UserStatus::ACTIVE->gradient())
);
expect(count($activeEmployees))->toBeGreaterThanOrEqual(1);
});
it('deletes a user successfully', function () {
$userToDelete = User::factory()
->has(Employee::factory())
->create();
it('filters employees by status - inactive', function () {
$activeUser = createIndexTestUser(['status' => UserStatus::ACTIVE]);
$activeUser->assignRole('Admin');
Livewire::actingAs($this->user)
->test(Index::class)
->call('delete', $userToDelete)
$inactiveUser = createIndexTestUser(['status' => UserStatus::INACTIVE]);
$inactiveUser->assignRole('Admin');
$component = mountIndexComponent($this->user);
$component->set('status', [UserStatus::INACTIVE->value]);
$employees = $component->get('employees');
$inactiveEmployees = collect($employees)->filter(
fn ($e) => $e['user']['status']['gradient'] === (UserStatus::INACTIVE->gradient())
);
expect(count($inactiveEmployees))->toBeGreaterThanOrEqual(1);
});
it('filters employees by multiple statuses', function () {
$activeUser = createIndexTestUser(['status' => UserStatus::ACTIVE]);
$activeUser->assignRole('Admin');
$inactiveUser = createIndexTestUser(['status' => UserStatus::INACTIVE]);
$inactiveUser->assignRole('Admin');
$component = mountIndexComponent($this->user);
$component->set('status', [
UserStatus::ACTIVE->value,
UserStatus::INACTIVE->value,
]);
expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2);
});
it('filters employees by both search and status', function () {
$activeUser = createIndexTestUser(
['status' => UserStatus::ACTIVE],
['full_name' => 'Active User']
);
$activeUser->assignRole('Admin');
$inactiveUser = createIndexTestUser(
['status' => UserStatus::INACTIVE],
['full_name' => 'Inactive User']
);
$inactiveUser->assignRole('Admin');
$component = mountIndexComponent($this->user)
->set('search', 'Active')
->set('status', [UserStatus::ACTIVE->value]);
$employees = $component->get('employees');
expect(count($employees))->toBeGreaterThanOrEqual(1);
expect($employees[0]['full_name'])->toBe('Active User');
});
it('loads employees ordered by latest', function () {
$user1 = createIndexTestUser([], ['full_name' => 'First Employee']);
$user1->assignRole('Admin');
sleep(1);
$user2 = createIndexTestUser([], ['full_name' => 'Second Employee']);
$user2->assignRole('Admin');
$component = mountIndexComponent($this->user);
$employees = $component->get('employees');
$firstEmployee = collect($employees)->first(fn ($e) => $e['full_name'] === 'Second Employee');
$secondEmployee = collect($employees)->first(fn ($e) => $e['full_name'] === 'First Employee');
expect($firstEmployee)->not->toBeNull();
expect($secondEmployee)->not->toBeNull();
// The second employee should appear before the first
$firstIndex = array_search('Second Employee', array_column($employees, 'full_name'));
$secondIndex = array_search('First Employee', array_column($employees, 'full_name'));
expect($firstIndex)->toBeLessThan($secondIndex);
});
it('loads employee with outlets', function () {
$outlet1 = Outlet::factory()->create(['name' => 'Outlet A']);
$outlet2 = Outlet::factory()->create(['name' => 'Outlet B']);
$user = createIndexTestUser();
$user->assignRole('Admin');
$user->outlets()->attach([$outlet1->id, $outlet2->id]);
$component = mountIndexComponent($this->user);
$employeeData = collect($component->get('employees'))->first(
fn ($e) => $e['full_name'] === $user->employee->full_name
);
expect($employeeData['outlets'])->toContain('Outlet A', 'Outlet B');
});
it('loads employee with roles', function () {
$user = createIndexTestUser();
$user->assignRole(['Admin', 'Leader']);
$component = mountIndexComponent($this->user);
$employeeData = collect($component->get('employees'))->first(
fn ($e) => $e['full_name'] === $user->employee->full_name
);
expect($employeeData['roles'])->toContain('Admin', 'Leader');
});
it('loads employee with referral code', function () {
$user = createIndexTestUser();
$user->assignRole('Admin');
$user->referralCode()->create(['code' => 'REF12345']);
$component = mountIndexComponent($this->user);
$employeeData = collect($component->get('employees'))->first(
fn ($e) => $e['full_name'] === $user->employee->full_name
);
expect($employeeData['referral_code'])->toBe('REF12345');
});
it('deletes an employee successfully', function () {
$employeeUser = createIndexTestUser();
$employeeUser->assignRole('Admin');
mountIndexComponent($this->user)
->call('delete', $employeeUser)
->assertHasNoErrors();
$this->assertSoftDeleted('users', ['id' => $userToDelete->id]);
$this->assertSoftDeleted('users', ['id' => $employeeUser->id]);
$this->assertSoftDeleted('employees', ['user_id' => $employeeUser->id]);
});
it('reloads employees after deletion', function () {
$employeeUser = createIndexTestUser();
$employeeUser->assignRole('Admin');
$component = mountIndexComponent($this->user);
$initialCount = count($component->get('employees'));
$component->call('delete', $employeeUser);
expect(count($component->get('employees')))->toBeLessThan($initialCount);
});
it('resets password successfully', function () {
$defaultPassword = config('myconfig.password_default');
$employeeUser = createIndexTestUser();
$employeeUser->assignRole('Admin');
$employeeUser->update(['password' => Hash::make('oldpassword')]);
$oldPasswordHash = $employeeUser->password;
mountIndexComponent($this->user)
->call('resetPassword', $employeeUser)
->assertHasNoErrors();
$employeeUser->refresh();
expect($employeeUser->password)->not->toBe($oldPasswordHash);
expect(Hash::check($defaultPassword, $employeeUser->password))->toBeTrue();
});
it('does not display delete and edit buttons for current authenticated user', function () {
$component = mountIndexComponent($this->user);
$component
->assertDontSee('Delete')
->assertDontSee('Edit');
});
it('updates search with debounce', function () {
$user1 = createIndexTestUser([], ['full_name' => 'John Doe']);
$user1->assignRole('Admin');
$component = mountIndexComponent($this->user);
$component->set('search', 'John');
expect($component->get('search'))->toBe('John');
});
it('updates status filter correctly', function () {
$component = mountIndexComponent($this->user);
$component->set('status', ['active']);
expect($component->get('status'))->toBeArray();
});