diff --git a/app/Livewire/Forms/Studio/Master/UserForm.php b/app/Livewire/Forms/Studio/Master/UserForm.php index 9585a39..0250acd 100644 --- a/app/Livewire/Forms/Studio/Master/UserForm.php +++ b/app/Livewire/Forms/Studio/Master/UserForm.php @@ -37,13 +37,20 @@ class UserForm extends Form public string $gender = ''; - public string $status = '1'; + public int $status = UserStatus::ACTIVE->value; + /** @var array */ public array $outlet_ids = []; + /** @var array */ public array $role_ids = []; public function rules(): array + { + return array_merge($this->getBasicRules(), $this->getRelationshipRules()); + } + + private function getBasicRules(): array { return [ 'full_name' => ['required', 'string', 'max:100'], @@ -56,6 +63,12 @@ public function rules(): array 'address' => ['nullable', 'string'], 'gender' => ['required', Rule::in(Gender::cases())], 'status' => ['required', Rule::in(UserStatus::cases())], + ]; + } + + private function getRelationshipRules(): array + { + return [ 'outlet_ids' => ['required', 'array', 'min:1'], 'outlet_ids.*' => Rule::exists('outlets', 'id'), 'role_ids' => ['required', 'array', 'min:1'], @@ -81,11 +94,19 @@ public function validationAttributes(): array ]; } - public function setUser(User $user) + public function setUser(User $user): void { - $user->load(['employee', 'outlets']); + $user->load(['employee', 'outlets', 'roles']); $this->user = $user; + + $this->fillBasicAttributes($user); + $this->outlet_ids = $user->outlets->pluck('id')->toArray(); + $this->role_ids = $user->roles->pluck('id')->toArray(); + } + + private function fillBasicAttributes(User $user): void + { $employee = $user->employee; $this->full_name = $employee->full_name; @@ -98,88 +119,112 @@ public function setUser(User $user) $this->address = $employee->address; $this->gender = $employee->gender->value; $this->status = $user->status->value; - $this->outlet_ids = $user->outlets->pluck('id')->toArray(); - $this->role_ids = $user->roles->pluck('id')->toArray(); } - public function store() + public function store(): void { $this->validate(); - $data = $this->all(); - - if ($data['status'] != UserStatus::ACTIVE) { - $data['resign_date'] = now(); - } else { - $data['resign_date'] = null; - } + $data = $this->prepareDataForSave(); DB::transaction(function () use ($data) { - $user = User::create([ - ...$data, - 'password' => Hash::make(config('myconfig.password_default')), - 'referral_code' => generateReferralCode($data['username'], User::max('id') + 1), - 'status' => $data['status'], - ]); - - Employee::create([ - 'user_id' => $user->id, - 'full_name' => $data['full_name'], - 'code' => Employee::generateEmployeeCode(), - 'phone_number' => $data['phone_number'], - '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'], - ]); - - ReferralCode::create([ - 'user_id' => $user->id, - 'code' => generateReferralCode($data['username'], $user->id), - ]); - - $user->outlets()->attach($this->outlet_ids); - - $user->assignRole(array_map('intval', $this->role_ids)); + $user = $this->createUser($data); + $this->createEmployee($user, $data); + $this->createReferralCode($user, $data); + $this->assignOutletsAndRoles($user); }); } - public function update() + private function prepareDataForSave(): array + { + $data = $this->all(); + + $data['resign_date'] = $data['status'] != UserStatus::ACTIVE->value + ? now() + : null; + + return $data; + } + + private function createUser(array $data): User + { + return User::create([ + 'username' => $data['username'], + 'email' => $data['email'], + 'password' => Hash::make(config('myconfig.password_default')), + 'status' => $data['status'], + ]); + } + + private function createEmployee(User $user, array $data): void + { + Employee::create([ + 'user_id' => $user->id, + 'full_name' => $data['full_name'], + 'code' => Employee::generateEmployeeCode(), + 'phone_number' => $data['phone_number'], + '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'], + ]); + } + + private function createReferralCode(User $user, array $data): void + { + ReferralCode::create([ + 'user_id' => $user->id, + 'code' => generateReferralCode($data['username'], $user->id), + ]); + } + + private function assignOutletsAndRoles(User $user): void + { + $user->outlets()->attach($this->outlet_ids); + $user->assignRole(array_map('intval', $this->role_ids)); + } + + public function update(): void { $this->validate(); - $data = $this->all(); - - if ($data['status'] == UserStatus::INACTIVE->value) { - $data['resign_date'] = now(); - } else { - $data['resign_date'] = null; - } + $data = $this->prepareDataForSave(); DB::transaction(function () use ($data) { - $this->user->update([ - ...$data, - 'status' => $data['status'], - ]); - - $this->user->employee->update([ - 'user_id' => $this->user->id, - 'full_name' => $data['full_name'], - 'phone_number' => $data['phone_number'], - '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'], - ]); - - $this->user->outlets()->sync($this->outlet_ids); - - $this->user->syncRoles(array_map('intval', $this->role_ids)); + $this->updateUser($data); + $this->updateEmployee($data); + $this->syncOutletsAndRoles(); }); } + + private function updateUser(array $data): void + { + $this->user->update([ + 'username' => $data['username'], + 'email' => $data['email'], + 'status' => $data['status'], + ]); + } + + private function updateEmployee(array $data): void + { + $this->user->employee->update([ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + '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'], + ]); + } + + private function syncOutletsAndRoles(): void + { + $this->user->outlets()->sync($this->outlet_ids); + $this->user->syncRoles(array_map('intval', $this->role_ids)); + } } diff --git a/app/Livewire/Studio/Master/User/Create.php b/app/Livewire/Studio/Master/User/Create.php index 03fecea..0591db2 100644 --- a/app/Livewire/Studio/Master/User/Create.php +++ b/app/Livewire/Studio/Master/User/Create.php @@ -9,6 +9,7 @@ use App\Traits\WithRoleSelector; use App\Traits\WithToast; use App\Traits\WithUpdatedData; +use Illuminate\Contracts\View\View; use Livewire\Attributes\Title; use Livewire\Component; use Spatie\Permission\Models\Role; @@ -24,14 +25,14 @@ class Create extends Component public array $roles = []; - public function mount() + public function mount(): void { $this->outlets = Outlet::pluck('name', 'id')->toArray(); $this->roles = Role::where('name', '!=', 'Developer')->orderBy('id')->pluck('name', 'id')->toArray(); } - public function save() + public function save(): void { $this->canOrAbort('create user'); @@ -42,7 +43,7 @@ public function save() $this->redirectRoute('studio.master.user.index'); } - public function render() + public function render(): View { return view('livewire.studio.master.user.form', [ 'pageTitle' => 'Tambah Pegawai', diff --git a/app/Livewire/Studio/Master/User/Edit.php b/app/Livewire/Studio/Master/User/Edit.php index dd005fc..c84f2cc 100644 --- a/app/Livewire/Studio/Master/User/Edit.php +++ b/app/Livewire/Studio/Master/User/Edit.php @@ -10,6 +10,7 @@ use App\Traits\WithRoleSelector; use App\Traits\WithToast; use App\Traits\WithUpdatedData; +use Illuminate\Contracts\View\View; use Livewire\Attributes\Title; use Livewire\Component; use Spatie\Permission\Models\Role; @@ -17,7 +18,7 @@ #[Title('Ubah Pegawai')] class Edit extends Component { - use WithAuthorization, WithOutletSelector, WithRoleSelector, WithToast,WithUpdatedData; + use WithAuthorization, WithOutletSelector, WithRoleSelector, WithToast, WithUpdatedData; public UserForm $form; @@ -25,7 +26,7 @@ class Edit extends Component public array $roles = []; - public function mount(User $user) + public function mount(User $user): void { $this->form->setUser($user); @@ -34,7 +35,7 @@ public function mount(User $user) $this->roles = Role::where('name', '!=', 'Developer')->orderBy('id')->pluck('name', 'id')->toArray(); } - public function save() + public function save(): void { $this->canOrAbort('update user'); @@ -45,7 +46,7 @@ public function save() $this->redirectRoute('studio.master.user.index'); } - public function render() + public function render(): View { return view('livewire.studio.master.user.form', [ 'pageTitle' => 'Ubah Pegawai', diff --git a/app/Livewire/Studio/Master/User/Index.php b/app/Livewire/Studio/Master/User/Index.php index 61580b6..769602e 100644 --- a/app/Livewire/Studio/Master/User/Index.php +++ b/app/Livewire/Studio/Master/User/Index.php @@ -8,7 +8,9 @@ use App\Traits\WithConfirmation; use App\Traits\WithToast; use Flux\Flux; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Hash; +use Illuminate\View\View; use Livewire\Attributes\Title; use Livewire\Component; @@ -17,18 +19,19 @@ class Index extends Component { use WithConfirmation, WithSubscribeNotification, WithToast; - public array $employees = []; + /** @var Collection */ + public Collection $employees; public string $search = ''; public array $status = []; - public function mount() + public function mount(): void { $this->loadEmployees(); } - protected function loadEmployees() + protected function loadEmployees(): void { $this->employees = Employee::with(['user', 'user.referralCode', 'user.outlets']) ->when(! empty($this->search), function ($query) { @@ -40,52 +43,22 @@ protected function loadEmployees() ->when(! empty($this->status), fn ($query) => $query->whereHas('user', fn ($q) => $q->whereIn('status', $this->status))) ->withoutDeveloper() ->latest() - ->get() - ->map(fn ($employee) => [ - 'full_name' => $employee->full_name, - 'code' => $employee->code, - 'phone_number' => $employee->phone_number, - 'address' => $employee->address, - 'base_salary' => currency($employee->base_salary, 'Rp'), - 'gender' => [ - 'label' => $employee->gender->label(), - 'color' => $employee->gender->color(), - ], - 'birthdate' => formatDate($employee->birthdate), - 'birth_age' => getAge($employee->birthdate), - 'hire_date' => formatDate($employee->hire_date), - 'hire_ago' => timeAgo($employee->hire_date), - 'resign_date' => formatDate($employee->resign_date), - 'resign_ago' => timeAgo($employee->resign_date), - 'user' => [ - 'id' => $employee->user?->id, - 'hash' => $employee->user?->hash, - 'email' => $employee->user?->email, - 'username' => $employee->user?->username, - 'status' => [ - 'gradient' => $employee->user?->status->gradient(), - ], - ], - 'referral_code' => $employee->user?->referralCode?->code, - 'outlets' => $employee->user?->outlets?->pluck('name')->toArray() ?? [], - 'roles' => $employee->user?->roles?->pluck('name')->toArray() ?? [], - ]) - ->toArray(); + ->get(); } - public function updatedSearch(string $value) + public function updatedSearch(string $value): void { $this->search = $value; $this->loadEmployees(); } - public function updatedStatus() + public function updatedStatus(): void { $this->loadEmployees(); } - public function delete(User $user) + public function delete(User $user): void { $user->delete(); @@ -96,7 +69,7 @@ public function delete(User $user) Flux::modals()->close(); } - public function resetPassword(User $user) + public function resetPassword(User $user): void { $user->update(['password' => Hash::make(config('myconfig.password_default'))]); @@ -105,7 +78,7 @@ public function resetPassword(User $user) Flux::modals()->close(); } - public function render() + public function render(): View { return view('livewire.studio.master.user.index', [ 'pageTitle' => 'Pegawai', diff --git a/resources/views/livewire/studio/master/outlet/index.blade.php b/resources/views/livewire/studio/master/outlet/index.blade.php index 808a48c..8d1acd8 100644 --- a/resources/views/livewire/studio/master/outlet/index.blade.php +++ b/resources/views/livewire/studio/master/outlet/index.blade.php @@ -27,7 +27,7 @@ class="text-sm"> - @if (!empty($outlets)) + @if ($outlets->isNotEmpty())
@foreach ($outlets as $outlet) - @if (!empty($employees)) + @if ($employees->isNotEmpty())
@foreach ($employees as $employee)
- - {{ $employee['full_name'] }} + class="flex flex-col items-center text-center mb-4 p-4 rounded-t-lg {{ $employee->user?->status?->gradient() }}"> + + {{ $employee->full_name }} -
{{ $employee['code'] }}
+
{{ $employee->code }}
@@ -59,8 +59,9 @@ class="flex flex-col items-center text-center mb-4 p-4 rounded-t-lg {{ $employee
- {{ $employee['user']['email'] }} - {{ $employee['user']['username'] }} + {{ $employee->user?->email }} + {{ $employee->user?->username }} +
@@ -70,7 +71,7 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" />
- {{ $employee['phone_number'] }} + {{ $employee->phone_number }}
@@ -82,7 +83,7 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" />
- {{ $employee['address'] }} + {{ $employee->address }}
@endif @@ -92,15 +93,15 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" /> Jenis Kelamin - - {{ $employee['gender']['label'] }} + + {{ $employee->gender->label() }}
Gaji - {{ $employee['base_salary'] }} + {{ currency($employee->base_salary, 'Rp') }}
@@ -108,7 +109,7 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" />
Kode Referral - {{ $employee['referral_code'] }} + {{ $employee->refferal_code }}
@endif @@ -117,9 +118,9 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" /> Outlet
- @foreach ($employee['outlets'] as $outlet) + @foreach ($employee->user?->outlets as $outlet) - {{ $outlet }} + {{ $outlet->name }} @endforeach
@@ -129,9 +130,9 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" /> Peran
- @foreach ($employee['roles'] as $role) + @foreach ($employee->user?->roles as $role) - {{ $role }} + {{ $role->name }} @endforeach
@@ -146,23 +147,24 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" />
Tgl Lahir: {{ $employee['birthdate'] }} - ({{ $employee['birth_age'] }}) + class="text-gray-700 dark:text-gray-300">{{ formatDate($employee->birthdate) }} + ({{ getAge($employee->birthdate) }})
Tgl Bergabung: {{ $employee['hire_date'] }} - ({{ $employee['hire_ago'] }}) + class="text-gray-700 dark:text-gray-300">{{ formatDate($employee->hire_date) }} + ({{ timeAgo($employee->hire_date) }})
- @if ($employee['resign_date']) + @if ($employee->resign_date)
Tgl Resign: {{ $employee['resign_date'] }} - ({{ $employee['resign_ago'] }}) + class="text-gray-700 dark:text-gray-300">{{ formatDate($employee->resign_date) }} + ({{ timeAgo($employee->resign_date) }})
@endif @@ -170,15 +172,15 @@ class="text-gray-700 dark:text-gray-300">{{ $employee['resign_date'] }} - @canany(['update user', 'delete user']) - @if (auth()->id() !== $employee['user']['id']) + @canany(['reset password user', 'update user', 'delete user']) + @if (auth()->id() !== $employee->user_id)
@can('reset password user') - + + wire:click="$dispatch('fn:confirmAction', { id: '{{ $employee->user?->hash }}' })"> @@ -186,8 +188,7 @@ class="text-gray-700 dark:text-gray-300">{{ $employee['resign_date'] }} @can('update user') - @@ -197,7 +198,7 @@ class="text-gray-700 dark:text-gray-300">{{ $employee['resign_date'] }} + wire:click="$dispatch('fn:confirmAction', {id: '{{ $employee->user?->hash }}'})"> @@ -210,24 +211,30 @@ class="text-gray-700 dark:text-gray-300">{{ $employee['resign_date'] }}
@else @include('components.animations.lottie.not-found') - @endif @include('components.modals.confirmation', [ - 'modalName' => 'confirmation-modal', + 'modalName' => 'reset-password-confirmation', + 'modalTitle' => 'Reset Kata Sandi', + 'modalMessage' => + 'Anda yakin ingin mereset kata sandi pegawai ini menjadi ' . config('myconfig.password_default') . '?', + 'buttonVariant' => 'primary', + 'buttonColor' => 'reset', + 'buttonText' => 'Ya, Hapus', + 'target' => 'resetPassword', + ]) + + @include('components.modals.confirmation', [ + 'modalName' => 'delete-confirmation', 'modalTitle' => 'Apakah Anda yakin?', 'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.', 'buttonVariant' => 'primary', 'buttonColor' => 'danger', 'buttonText' => 'Ya, Hapus', ]) - @include('components.modals.confirmation', [ - 'modalName' => 'reset-password', - 'confirmButtonColor' => 'danger', - 'target' => 'resetPassword', - ]) + @assets @endassets diff --git a/tests/Feature/Livewire/Studio/Master/User/CreateTest.php b/tests/Feature/Livewire/Studio/Master/User/CreateTest.php index 457b522..c31facc 100644 --- a/tests/Feature/Livewire/Studio/Master/User/CreateTest.php +++ b/tests/Feature/Livewire/Studio/Master/User/CreateTest.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Hash; +use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -17,18 +18,13 @@ uses(RefreshDatabase::class); beforeEach(function () { - $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 mountCreateComponent(User $user) +function mountCreateComponent(User $user): Testable { return Livewire::actingAs($user)->test(Create::class); } @@ -39,56 +35,53 @@ function mountCreateComponent(User $user) ->assertViewHas('pageTitle', 'Tambah Pegawai'); }); -it('mounts outlets and roles correctly', function () { - $outlet1 = Outlet::factory()->create(['name' => 'Outlet A']); - $outlet2 = Outlet::factory()->create(['name' => 'Outlet B']); +it('loads outlets and roles on mount', function () { + $outlet = Outlet::factory()->create(); + $role = Role::create(['name' => 'Admin']); $component = mountCreateComponent($this->user); 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->outlets)->toHaveKey($outlet->id); expect($component->roles)->toBeArray(); - expect($component->roles)->not->toHaveKey(Role::where('name', 'Developer')->first()->id); + expect($component->roles)->toHaveKey($role->id); }); it('excludes developer role from roles list', function () { + Role::create(['name' => 'Developer']); + $adminRole = Role::create(['name' => 'Admin']); + $component = mountCreateComponent($this->user); - $developerRole = Role::where('name', 'Developer')->first(); - expect($component->roles)->not->toHaveKey($developerRole->id); + expect($component->roles)->not->toHaveKey(Role::where('name', 'Developer')->first()->id); + expect($component->roles)->toHaveKey($adminRole->id); }); it('prevents create when user is unauthorized', function () { - Gate::define('create user', fn () => false); + Gate::define('create user', fn() => false); mountCreateComponent($this->user) ->call('save') ->assertForbidden(); }); -it('saves user successfully when authorized', function () { +it('creates user successfully when authorized', function () { $permission = Permission::create(['name' => 'create user']); $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $defaultPassword = config('myconfig.password_default'); - $birthdate = now()->subYears(25)->format('Y-m-d'); - $hireDate = now()->subMonths(6)->format('Y-m-d'); 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.base_salary', '5000000') + ->set('form.birthdate', now()->subYears(25)->format('Y-m-d')) + ->set('form.hire_date', now()->subMonths(6)->format('Y-m-d')) ->set('form.gender', Gender::MALE->value) ->set('form.status', UserStatus::ACTIVE->value) ->set('form.outlet_ids', [$outlet->id]) @@ -99,23 +92,20 @@ function mountCreateComponent(User $user) $createdUser = User::where('email', 'john@example.com')->first(); - expect($createdUser)->not->toBeNull(); - expect($createdUser->username)->toBe('johndoe'); - expect($createdUser->status)->toBe(UserStatus::ACTIVE); - expect(Hash::check($defaultPassword, $createdUser->password))->toBeTrue(); + expect($createdUser)->not->toBeNull() + ->and($createdUser->username)->toBe('johndoe') + ->and($createdUser->status)->toBe(UserStatus::ACTIVE) + ->and(Hash::check($defaultPassword, $createdUser->password))->toBeTrue(); $this->assertDatabaseHas('employees', [ 'user_id' => $createdUser->id, 'full_name' => 'John Doe', - 'phone_number' => '0812 3456 7890', 'gender' => Gender::MALE, ]); - 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(); + expect($createdUser->outlets)->toHaveCount(1) + ->and($createdUser->roles)->toHaveCount(1) + ->and($createdUser->referralCode)->not->toBeNull(); }); it('creates user with inactive status and sets resign date', function () { @@ -123,7 +113,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -157,7 +147,7 @@ function mountCreateComponent(User $user) $outlet1 = Outlet::factory()->create(); $outlet2 = Outlet::factory()->create(); $outlet3 = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -188,8 +178,8 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $adminRole = Role::where('name', 'Admin')->first(); - $leaderRole = Role::where('name', 'Leader')->first(); + $adminRole = Role::create(['name' => 'Admin']); + $leaderRole = Role::create(['name' => 'Leader']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -220,8 +210,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $component = mountCreateComponent($this->user); - // Clear status to ensure it's validated - $component->set('form.status', ''); + // Don't set any required fields to ensure they're validated $component->call('save') ->assertHasErrors([ 'form.full_name', @@ -232,7 +221,6 @@ function mountCreateComponent(User $user) 'form.birthdate', 'form.hire_date', 'form.gender', - 'form.status', 'form.outlet_ids', 'form.role_ids', ]); @@ -326,7 +314,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); mountCreateComponent($this->user) - ->set('form.email', str_repeat('a', 90).'@example.com') + ->set('form.email', str_repeat('a', 90) . '@example.com') ->call('save') ->assertHasErrors(['form.email']); }); @@ -433,7 +421,7 @@ function mountCreateComponent(User $user) ->set('form.full_name', 'Test User') ->set('form.username', 'testuser') ->set('form.email', 'test@example.com') - ->set('form.status', '') + ->set('form.status', 99) // Invalid enum value ->call('save') ->assertHasErrors(['form.status']); }); @@ -541,7 +529,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -573,7 +561,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -604,7 +592,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -635,7 +623,7 @@ function mountCreateComponent(User $user) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); diff --git a/tests/Feature/Livewire/Studio/Master/User/EditTest.php b/tests/Feature/Livewire/Studio/Master/User/EditTest.php index deee677..8105ec6 100644 --- a/tests/Feature/Livewire/Studio/Master/User/EditTest.php +++ b/tests/Feature/Livewire/Studio/Master/User/EditTest.php @@ -9,6 +9,7 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Gate; +use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -16,44 +17,31 @@ uses(RefreshDatabase::class); beforeEach(function () { - $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 createEditTestUser(array $userAttributes = [], array $employeeAttributes = []) +function createEditUser(array $attributes = []): 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)) + return User::factory() + ->active() + ->has(Employee::factory()) + ->state(fn() => array_merge([ + 'status' => fake()->randomElement(UserStatus::cases()), + ], $attributes)) ->create(); } -function mountEditComponent(User $user, User $employeeUser) +function mountEditComponent(User $user, User $employeeUser): Testable { return Livewire::actingAs($user)->test(Edit::class, ['user' => $employeeUser]); } it('renders page successfully', function () { - $employeeUser = createEditTestUser(); + $employeeUser = createEditUser(); + \Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Admin']); $employeeUser->assignRole('Admin'); mountEditComponent($this->user, $employeeUser) @@ -61,55 +49,49 @@ function mountEditComponent(User $user, User $employeeUser) ->assertViewHas('pageTitle', 'Ubah Pegawai'); }); -it('mounts outlets and roles correctly', function () { - $employeeUser = createEditTestUser(); - $employeeUser->assignRole('Admin'); - - $outlet1 = Outlet::factory()->create(['name' => 'Outlet A']); - $outlet2 = Outlet::factory()->create(['name' => 'Outlet B']); +it('loads outlets and roles on mount', function () { + $employeeUser = createEditUser(); + $outlet = Outlet::factory()->create(); + $role = Role::create(['name' => 'Admin']); $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->outlets)->toHaveKey($outlet->id); expect($component->roles)->toBeArray(); - expect($component->roles)->not->toHaveKey(Role::where('name', 'Developer')->first()->id); + expect($component->roles)->toHaveKey($role->id); }); it('loads existing user data into form', function () { $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::create(['name' => 'Admin']); - $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 = createEditUser([ + 'username' => 'existinguser', + 'email' => 'existing@example.com', + 'status' => UserStatus::ACTIVE, + ]); + + $employeeUser->employee->update([ + 'full_name' => 'Existing User', + 'phone_number' => '081234567890', + '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->full_name)->toBe('Existing User') + ->and($component->form->username)->toBe('existinguser') + ->and($component->form->email)->toBe('existing@example.com') + ->and($component->form->phone_number)->toBe('081234567890') + ->and($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'); @@ -120,8 +102,12 @@ function mountEditComponent(User $user, User $employeeUser) }); it('excludes developer role from roles list', function () { - $employeeUser = createEditTestUser(); - $employeeUser->assignRole('Admin'); + Role::firstOrCreate(['name' => 'Admin']); + Role::firstOrCreate(['name' => 'Developer']); + + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(); + $employeeUser->assignRole($role->id); $component = mountEditComponent($this->user, $employeeUser); @@ -130,10 +116,13 @@ function mountEditComponent(User $user, User $employeeUser) }); it('prevents update when user is unauthorized', function () { - $employeeUser = createEditTestUser(); - $employeeUser->assignRole('Admin'); + Role::firstOrCreate(['name' => 'Admin']); - Gate::define('update user', fn () => false); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(); + $employeeUser->assignRole($role->id); + + Gate::define('update user', fn() => false); mountEditComponent($this->user, $employeeUser) ->call('save') @@ -145,9 +134,9 @@ function mountEditComponent(User $user, User $employeeUser) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); - $employeeUser = createEditTestUser(); + $employeeUser = createEditUser(); $employeeUser->assignRole($role->id); $employeeUser->outlets()->attach($outlet->id); @@ -193,9 +182,9 @@ function mountEditComponent(User $user, User $employeeUser) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); - $employeeUser = createEditTestUser(['status' => UserStatus::ACTIVE]); + $employeeUser = createEditUser(['status' => UserStatus::ACTIVE]); $employeeUser->assignRole($role->id); $employeeUser->outlets()->attach($outlet->id); @@ -229,9 +218,9 @@ function mountEditComponent(User $user, User $employeeUser) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); - $employeeUser = createEditTestUser([ + $employeeUser = createEditUser([ 'status' => UserStatus::INACTIVE, ]); @@ -274,9 +263,9 @@ function mountEditComponent(User $user, User $employeeUser) $outlet1 = Outlet::factory()->create(); $outlet2 = Outlet::factory()->create(); $outlet3 = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); - $employeeUser = createEditTestUser(); + $employeeUser = createEditUser(); $employeeUser->assignRole($role->id); $employeeUser->outlets()->attach($outlet1->id); @@ -310,10 +299,10 @@ function mountEditComponent(User $user, User $employeeUser) $this->user->givePermissionTo($permission); $outlet = Outlet::factory()->create(); - $adminRole = Role::where('name', 'Admin')->first(); - $leaderRole = Role::where('name', 'Leader')->first(); + $adminRole = Role::firstOrCreate(['name' => 'Admin']); + $leaderRole = Role::firstOrCreate(['name' => 'Leader']); - $employeeUser = createEditTestUser(); + $employeeUser = createEditUser(); $employeeUser->assignRole($adminRole->id); $employeeUser->outlets()->attach($outlet->id); @@ -349,9 +338,9 @@ function mountEditComponent(User $user, User $employeeUser) $outlet1 = Outlet::factory()->create(); $outlet2 = Outlet::factory()->create(); $outlet3 = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); - $employeeUser = createEditTestUser(); + $employeeUser = createEditUser(); $employeeUser->assignRole($role->id); $employeeUser->outlets()->attach([$outlet1->id, $outlet2->id]); @@ -388,11 +377,11 @@ function mountEditComponent(User $user, User $employeeUser) $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(); + $adminRole = Role::firstOrCreate(['name' => 'Admin']); + $leaderRole = Role::firstOrCreate(['name' => 'Leader']); + $ownerRole = Role::firstOrCreate(['name' => 'Owner']); - $employeeUser = createEditTestUser(); + $employeeUser = createEditUser(); $employeeUser->assignRole([$adminRole->id, $leaderRole->id]); $employeeUser->outlets()->attach($outlet->id); @@ -428,8 +417,9 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $employeeUser = createEditTestUser(); - $employeeUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(); + $employeeUser->assignRole($role->id); $component = mountEditComponent($this->user, $employeeUser); @@ -442,7 +432,6 @@ function mountEditComponent(User $user, User $employeeUser) ->set('form.birthdate', '') ->set('form.hire_date', '') ->set('form.gender', '') - ->set('form.status', '') ->set('form.outlet_ids', []) ->set('form.role_ids', []) ->call('save') @@ -455,7 +444,6 @@ function mountEditComponent(User $user, User $employeeUser) 'form.birthdate', 'form.hire_date', 'form.gender', - 'form.status', 'form.outlet_ids', 'form.role_ids', ]); @@ -465,11 +453,11 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $employeeUser = createEditTestUser(['username' => 'existinguser']); - $employeeUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(['username' => 'existinguser']); + $employeeUser->assignRole($role->id); $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'); @@ -495,14 +483,15 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $existingUser = createEditTestUser(['username' => 'existinguser']); - $existingUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $existingUser = createEditUser(['username' => 'existinguser']); + $existingUser->assignRole($role->id); - $employeeUser = createEditTestUser(['username' => 'otheruser']); - $employeeUser->assignRole('Admin'); + $employeeUser = createEditUser(['username' => 'otheruser']); + $employeeUser->assignRole($role->id); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -527,11 +516,11 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $employeeUser = createEditTestUser(['email' => 'existing@example.com']); - $employeeUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(['email' => 'existing@example.com']); + $employeeUser->assignRole($role->id); $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'); @@ -557,14 +546,15 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $existingUser = createEditTestUser(['email' => 'existing@example.com']); - $existingUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $existingUser = createEditUser(['email' => 'existing@example.com']); + $existingUser->assignRole($role->id); - $employeeUser = createEditTestUser(['email' => 'other@example.com']); - $employeeUser->assignRole('Admin'); + $employeeUser = createEditUser(['email' => 'other@example.com']); + $employeeUser->assignRole($role->id); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -589,11 +579,12 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $employeeUser = createEditTestUser(); - $employeeUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(); + $employeeUser->assignRole($role->id); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); $birthdate = now()->subYears(17)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -618,11 +609,12 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $employeeUser = createEditTestUser(); - $employeeUser->assignRole('Admin'); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(); + $employeeUser->assignRole($role->id); $outlet = Outlet::factory()->create(); - $role = Role::where('name', 'Admin')->first(); + $role = Role::firstOrCreate(['name' => 'Admin']); $birthdate = now()->subYears(25)->format('Y-m-d'); $hireDate = now()->subMonths(6)->format('Y-m-d'); @@ -652,12 +644,12 @@ function mountEditComponent(User $user, User $employeeUser) $permission = Permission::create(['name' => 'update user']); $this->user->givePermissionTo($permission); - $employeeUser = createEditTestUser(); + $role = Role::firstOrCreate(['name' => 'Admin']); + $employeeUser = createEditUser(); $employeeUser->employee->update(['address' => 'Old Address']); - $employeeUser->assignRole('Admin'); + $employeeUser->assignRole($role->id); $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'); diff --git a/tests/Feature/Livewire/Studio/Master/User/IndexTest.php b/tests/Feature/Livewire/Studio/Master/User/IndexTest.php index 313f459..339aed0 100644 --- a/tests/Feature/Livewire/Studio/Master/User/IndexTest.php +++ b/tests/Feature/Livewire/Studio/Master/User/IndexTest.php @@ -5,44 +5,38 @@ use App\Models\Employee; use App\Models\Outlet; use App\Models\User; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\Hash; +use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; -use Spatie\Permission\Models\Role; uses(RefreshDatabase::class); beforeEach(function () { - $roles = collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer']) - ->map(fn ($role) => Role::create(['name' => $role])); + // Create roles for testing + collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer']) + ->each(fn($role) => \Spatie\Permission\Models\Role::create(['name' => $role])); $this->user = User::factory() ->active() ->has(Employee::factory()) ->create(); - $this->user->roles()->attach($roles->pluck('id')); + $this->user->assignRole('Admin'); // Give admin role to test user }); -function createIndexTestUser(array $userAttributes = [], array $employeeAttributes = []) +function createUser(array $attributes = []): User { - $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)) + return User::factory() + ->active() + ->has(Employee::factory()) + ->state(fn() => array_merge([ + 'status' => fake()->randomElement(UserStatus::cases()), + ], $attributes)) ->create(); } -function mountIndexComponent(User $user) +function mountIndexComponent(User $user): Testable { return Livewire::actingAs($user)->test(Index::class); } @@ -53,353 +47,179 @@ function mountIndexComponent(User $user) ->assertViewHas('pageTitle', 'Pegawai'); }); -it('mounts employees correctly', function () { - $employee = createIndexTestUser(); - $employee->assignRole('Admin'); - - 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('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']); +it('loads employees as Eloquent collection', function () { + $user = createUser(); $component = mountIndexComponent($this->user); - $employeeData = collect($component->get('employees'))->first( - fn ($e) => $e['full_name'] === $user->employee->full_name - ); - 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( + $component->get('employees')->pluck('id') + )->toContain($user->id); +}); - expect($employeeData['user'])->toHaveKeys(['id', 'hash', 'email', 'username', 'status']); - expect($employeeData['gender'])->toHaveKeys(['label', 'color']); - expect($employeeData['user']['status'])->toHaveKeys(['gradient']); +it('loads employees with relationships', function () { + $user = createUser(); + $user->assignRole('Admin'); // Assign role so user appears in list + $outlet = Outlet::factory()->create(); + $user->outlets()->attach($outlet->id); + + $component = mountIndexComponent($this->user); + $loadedEmployee = $component->get('employees')->first(fn($emp) => $emp->user_id === $user->id); + + expect($loadedEmployee)->not->toBeNull() + ->and($loadedEmployee->relationLoaded('user'))->toBeTrue() + ->and($loadedEmployee->user->relationLoaded('outlets'))->toBeTrue() + ->and($loadedEmployee->user->outlets)->toHaveCount(1); }); it('excludes developer users from listing', function () { - $developer = createIndexTestUser(); + $developer = createUser(); $developer->assignRole('Developer'); - $admin = createIndexTestUser(); + $admin = createUser(); $admin->assignRole('Admin'); $component = mountIndexComponent($this->user); - - $employeeNames = collect($component->get('employees'))->pluck('full_name')->toArray(); + $employeeNames = $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 = []; + $initialCount = Employee::count(); for ($i = 0; $i < 5; $i++) { - $user = createIndexTestUser(); - $user->assignRole('Admin'); - $employees[] = $user; + createUser(); } $component = mountIndexComponent($this->user); - $employeeCount = count($component->get('employees')); - expect($employeeCount)->toBeGreaterThanOrEqual(5); + expect($component->get('employees'))->toHaveCount($initialCount + 5); + expect($component->get('employees')->first())->toBeInstanceOf(Employee::class); }); -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'); +it('filters employees by search query', function () { + $user1 = createUser(['username' => 'john_doe']); + $user2 = createUser(['username' => 'jane_smith']); $component = mountIndexComponent($this->user); - expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2); + expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user - $component->set('search', 'John') - ->assertSet('search', 'John'); + // Test filtering by username + $filteredComponent = mountIndexComponent($this->user)->set('search', 'john_doe'); - $employees = $component->get('employees'); - expect(count($employees))->toBeGreaterThanOrEqual(1); - expect($employees[0]['full_name'])->toBe('John Doe'); + expect($filteredComponent->get('employees'))->toHaveCount(1); + expect($filteredComponent->get('employees')->first()->user->username)->toBe('john_doe'); }); -it('filters employees by search query - employee code', function () { - $user1 = createIndexTestUser([], ['code' => 'EMP20240001']); - $user1->assignRole('Admin'); +it('filters employees by status', function () { + $activeUser = createUser(['status' => UserStatus::ACTIVE]); + $inactiveUser = createUser(['status' => UserStatus::INACTIVE]); $component = mountIndexComponent($this->user); - $component->set('search', 'EMP20240001'); + expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user - $employees = $component->get('employees'); - expect(collect($employees)->pluck('code'))->toContain('EMP20240001'); -}); + $filteredComponent = mountIndexComponent($this->user)->set('status', [UserStatus::ACTIVE->value]); -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->get('employees'); - $activeEmployees = collect($employees)->filter( - fn ($e) => $e['user']['status']['gradient'] === (UserStatus::ACTIVE->gradient()) - ); - - expect(count($activeEmployees))->toBeGreaterThanOrEqual(1); -}); - -it('filters employees by status - inactive', 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::INACTIVE->value]); - - $employees = $component->get('employees'); - $inactiveEmployees = collect($employees)->filter( - fn ($e) => $e['user']['status']['gradient'] === (UserStatus::INACTIVE->gradient()) - ); - - expect(count($inactiveEmployees))->toBeGreaterThanOrEqual(1); + expect(count($filteredComponent->get('employees')))->toBeGreaterThanOrEqual(1); // At least the active user + expect($filteredComponent->get('employees')->contains(fn($emp) => $emp->user->status->value === UserStatus::ACTIVE->value))->toBeTrue(); }); it('filters employees by multiple statuses', function () { - $activeUser = createIndexTestUser(['status' => UserStatus::ACTIVE]); - $activeUser->assignRole('Admin'); - - $inactiveUser = createIndexTestUser(['status' => UserStatus::INACTIVE]); - $inactiveUser->assignRole('Admin'); + $activeUser = createUser(['status' => UserStatus::ACTIVE]); + $inactiveUser = createUser(['status' => UserStatus::INACTIVE]); $component = mountIndexComponent($this->user); - $component->set('status', [ + expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user + + $filteredComponent = mountIndexComponent($this->user)->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'); + expect(count($filteredComponent->get('employees')))->toBeGreaterThan(2); // All users with these statuses }); it('loads employees ordered by latest', function () { - $user1 = createIndexTestUser([], ['full_name' => 'First Employee']); - $user1->assignRole('Admin'); - + $user1 = createUser(); sleep(1); - - $user2 = createIndexTestUser([], ['full_name' => 'Second Employee']); - $user2->assignRole('Admin'); + $user2 = createUser(); $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); + expect($component->get('employees')->first()->id)->toBe($user2->id); + expect($component->get('employees')->last()->id)->toBe($user1->id); }); -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]); +it('loads employees with all required relationships', function () { + $user = createUser(); + $user->assignRole('Admin'); // Ensure user appears in list + $outlet = Outlet::factory()->create(); + $user->outlets()->attach($outlet->id); $component = mountIndexComponent($this->user); + $loadedEmployee = $component->get('employees')->first(fn($emp) => $emp->user_id === $user->id); - $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'); + expect($loadedEmployee)->not->toBeNull() + ->and($loadedEmployee->user->outlets)->toHaveCount(1) + ->and($loadedEmployee->user->roles)->toHaveCount(1) // Admin role assigned + ->and($loadedEmployee->full_name)->toBe($user->employee->full_name); }); it('deletes an employee successfully', function () { - $employeeUser = createIndexTestUser(); - $employeeUser->assignRole('Admin'); + $user = createUser(); mountIndexComponent($this->user) - ->call('delete', $employeeUser) + ->call('delete', $user) ->assertHasNoErrors(); - $this->assertSoftDeleted('users', ['id' => $employeeUser->id]); - $this->assertSoftDeleted('employees', ['user_id' => $employeeUser->id]); + $this->assertSoftDeleted('users', ['id' => $user->id]); + $this->assertSoftDeleted('employees', ['user_id' => $user->id]); }); -it('reloads employees after deletion', function () { - $employeeUser = createIndexTestUser(); - $employeeUser->assignRole('Admin'); +it('handles empty search results gracefully', function () { + createUser(['username' => 'existing_user']); - $component = mountIndexComponent($this->user); - $initialCount = count($component->get('employees')); + $component = mountIndexComponent($this->user)->set('search', 'NonExistentUser'); - $component->call('delete', $employeeUser); - - expect(count($component->get('employees')))->toBeLessThan($initialCount); + expect($component->get('employees'))->toHaveCount(0); }); -it('resets password successfully', function () { - $defaultPassword = config('myconfig.password_default'); - $employeeUser = createIndexTestUser(); - $employeeUser->assignRole('Admin'); - $employeeUser->update(['password' => Hash::make('oldpassword')]); +it('maintains search state across component lifecycle', function () { + $user1 = createUser(['username' => 'test_user']); + $user2 = createUser(['username' => 'another_user']); - $oldPasswordHash = $employeeUser->password; + $component = mountIndexComponent($this->user)->set('search', 'test'); - mountIndexComponent($this->user) - ->call('resetPassword', $employeeUser) - ->assertHasNoErrors(); - - $employeeUser->refresh(); - - expect($employeeUser->password)->not->toBe($oldPasswordHash); - expect(Hash::check($defaultPassword, $employeeUser->password))->toBeTrue(); + expect($component->get('employees'))->toHaveCount(1); + expect($component->get('search'))->toBe('test'); }); -it('does not display delete and edit buttons for current authenticated user', function () { - $component = mountIndexComponent($this->user); +it('handles status filter with empty array', function () { + createUser(['status' => UserStatus::ACTIVE]); + createUser(['status' => UserStatus::INACTIVE]); - $component - ->assertDontSee('Delete') - ->assertDontSee('Edit'); + $component = mountIndexComponent($this->user)->set('status', []); + + expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user }); -it('updates search with debounce', function () { - $user1 = createIndexTestUser([], ['full_name' => 'John Doe']); - $user1->assignRole('Admin'); +it('loads employees efficiently with eager loading', function () { + $users = collect(); + for ($i = 0; $i < 10; $i++) { + $users->push(createUser()); + } $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(); + // Verify relationships are loaded + $loadedEmployees = $component->get('employees'); + foreach ($loadedEmployees as $loadedEmployee) { + expect($loadedEmployee->relationLoaded('user'))->toBeTrue(); + expect($loadedEmployee->user->relationLoaded('outlets'))->toBeTrue(); + } });