test: Add feature tests for user creation, editing, and index page, and refactor user index Livewire component data fetching.

This commit is contained in:
Yoga Pangestu 2026-01-02 17:03:01 +07:00
parent 39c0652e3e
commit 8358e20575
4 changed files with 284 additions and 36 deletions

View File

@ -21,52 +21,16 @@ class Index extends Component
{
use WithAuthorization, WithConfirmation, WithRequestNotification, WithSubscribeNotification, WithToast;
public Collection $employees;
public string $search = '';
public array $status = [];
public function mount(): void
{
$this->loadEmployees();
}
protected function loadEmployees(): void
{
$this->employees = Employee::with(['user', 'user.referralCode', 'user.outlets'])
->when(! empty($this->search), function ($query) {
return $query->where('full_name', 'like', '%'.$this->search.'%')
->orWhere('code', 'like', '%'.$this->search.'%')
->orWhereHas('user', fn ($q) => $q->where('email', 'like', '%'.$this->search.'%'))
->orWhereHas('user', fn ($q) => $q->where('username', 'like', '%'.$this->search.'%'));
})
->when(! empty($this->status), fn ($query) => $query->whereHas('user', fn ($q) => $q->whereIn('status', $this->status)))
->withoutDeveloper()
->latest()
->get();
}
public function updatedSearch(string $value): void
{
$this->search = $value;
$this->loadEmployees();
}
public function updatedStatus(): void
{
$this->loadEmployees();
}
public function delete(User $user): void
{
$this->canOrAbort('delete user');
$user->delete();
$this->loadEmployees();
$this->toast('Pegawai berhasil dihapus.');
Flux::modals()->close();
@ -85,8 +49,21 @@ public function resetPassword(User $user): void
public function render(): View
{
$employees = Employee::with(['user', 'user.referralCode', 'user.outlets'])
->when(! empty($this->search), function ($query) {
return $query->where('full_name', 'like', '%' . $this->search . '%')
->orWhere('code', 'like', '%' . $this->search . '%')
->orWhereHas('user', fn($q) => $q->where('email', 'like', '%' . $this->search . '%'))
->orWhereHas('user', fn($q) => $q->where('username', 'like', '%' . $this->search . '%'));
})
->when(! empty($this->status), fn($query) => $query->whereHas('user', fn($q) => $q->whereIn('status', $this->status)))
->withoutDeveloper()
->latest()
->get();
return view('livewire.studio.master.user.index', [
'pageTitle' => 'Pegawai',
'employees' => $employees,
]);
}
}

View File

@ -0,0 +1,109 @@
<?php
use App\Enums\Gender;
use App\Enums\UserStatus;
use App\Livewire\Studio\Master\User\Create;
use App\Models\Outlet;
use App\Models\User;
use Carbon\Carbon;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
beforeEach(function () {
$this->setupUser();
$role = Role::firstOrCreate(['name' => 'Owner']);
Permission::firstOrCreate(['name' => 'create user']);
$role->givePermissionTo('create user');
$this->user->assignRole($role);
});
it('renders the create user page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.master.user.create'))
->assertOk()
->assertSeeLivewire(Create::class);
});
it('validates required fields', function () {
Livewire::actingAs($this->user)
->test(Create::class)
->call('save')
->assertHasErrors([
'form.full_name' => 'required',
'form.username' => 'required',
'form.email' => 'required',
'form.phone_number' => 'required',
'form.base_salary' => 'required',
'form.birthdate' => 'required',
'form.hire_date' => 'required',
'form.gender' => 'required',
'form.outlet_ids' => 'required',
'form.role_ids' => 'required',
]);
});
it('validates username minimum length', function () {
Livewire::actingAs($this->user)
->test(Create::class)
->set('form.username', 'abc')
->call('save')
->assertHasErrors(['form.username' => 'min']);
});
it('validates birthdate must be at least 18 years ago', function () {
Livewire::actingAs($this->user)
->test(Create::class)
->set('form.birthdate', Carbon::now()->subYears(17)->format('Y-m-d'))
->call('save')
->assertHasErrors(['form.birthdate' => 'before']);
});
it('can store a new user/employee', function () {
$outlet = Outlet::factory()->create();
$role = Role::firstOrCreate(['name' => 'Admin']);
Livewire::actingAs($this->user)
->test(Create::class)
->set('form.full_name', 'Pegawai Baru')
->set('form.username', 'pegawaibaru')
->set('form.email', 'pegawai@example.com')
->set('form.phone_number', '0812 3456 7890')
->set('form.base_salary', '5.000.000')
->set('form.birthdate', '2000-01-01')
->set('form.hire_date', '2024-01-01')
->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(route('studio.master.user.index', ['notification' => 'Pegawai berhasil ditambahkan.']));
$this->assertDatabaseHas('users', [
'username' => 'pegawaibaru',
'email' => 'pegawai@example.com',
]);
$user = User::where('username', 'pegawaibaru')->first();
$this->assertDatabaseHas('employees', [
'user_id' => $user->id,
'full_name' => 'Pegawai Baru',
'base_salary' => 5000000,
]);
expect($user->outlets)->toHaveCount(1);
expect($user->hasRole('Admin'))->toBeTrue();
});
it('cannot create user without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Create::class)
->set('form.full_name', 'Unauthorized User')
->call('save')
->assertForbidden();
});

View File

@ -0,0 +1,64 @@
<?php
use App\Enums\UserStatus;
use App\Livewire\Studio\Master\User\Edit;
use App\Models\Employee;
use App\Models\User;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
beforeEach(function () {
$this->setupUser();
$role = Role::firstOrCreate(['name' => 'Owner']);
Permission::firstOrCreate(['name' => 'update user']);
$role->givePermissionTo('update user');
$this->user->assignRole($role);
$this->targetUser = User::factory()->create();
$this->targetEmployee = Employee::factory()->for($this->targetUser)->create();
$this->targetUser->outlets()->attach($this->outlet);
$this->targetUser->assignRole(Role::firstOrCreate(['name' => 'Admin']));
});
it('renders the edit user page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.master.user.edit', $this->targetUser))
->assertOk()
->assertSeeLivewire(Edit::class);
});
it('loads existing user data correctly', function () {
Livewire::actingAs($this->user)
->test(Edit::class, ['user' => $this->targetUser])
->assertSet('form.full_name', $this->targetEmployee->full_name)
->assertSet('form.username', $this->targetUser->username);
});
it('can update a user', function () {
Livewire::actingAs($this->user)
->test(Edit::class, ['user' => $this->targetUser])
->set('form.full_name', 'Pegawai Diperbarui')
->set('form.status', UserStatus::INACTIVE->value)
->call('save')
->assertHasNoErrors()
->assertRedirect(route('studio.master.user.index', ['notification' => 'Pegawai berhasil diperbarui.']));
$this->targetUser->refresh();
$this->targetEmployee->refresh();
expect($this->targetEmployee->full_name)->toBe('Pegawai Diperbarui');
expect($this->targetUser->status)->toBe(UserStatus::INACTIVE);
});
it('cannot update user without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Edit::class, ['user' => $this->targetUser])
->set('form.full_name', 'Unauthorized Edit')
->call('save')
->assertForbidden();
});

View File

@ -0,0 +1,98 @@
<?php
use App\Livewire\Studio\Master\User\Index;
use App\Models\Employee;
use App\Models\User;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
beforeEach(function () {
$this->setupUser();
$role = Role::firstOrCreate(['name' => 'Owner']);
Permission::firstOrCreate(['name' => 'view user']);
Permission::firstOrCreate(['name' => 'delete user']);
Permission::firstOrCreate(['name' => 'reset password user']);
$role->givePermissionTo(['view user', 'delete user', 'reset password user']);
$this->user->assignRole($role);
});
it('renders the user index page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.master.user.index'))
->assertOk()
->assertSeeLivewire(Index::class);
});
it('displays list of employees correctly', function () {
$employees = collect();
foreach (range(1, 3) as $i) {
$employees->push(Employee::factory()->create());
}
Livewire::actingAs($this->user)
->test(Index::class)
->assertViewHas('employees', function ($viewEmployees) use ($employees) {
return $viewEmployees->count() >= 3;
})
->assertSee($employees->first()->full_name)
->assertSee($employees->last()->full_name);
});
it('can search employees by name', function () {
$targetEmployee = Employee::factory()->create(['full_name' => 'Pegawai Spesifik']);
$otherEmployee = Employee::factory()->create(['full_name' => 'Biasa Saja']);
Livewire::actingAs($this->user)
->test(Index::class)
->set('search', 'Spesifik')
->assertSee($targetEmployee->full_name)
->assertDontSee($otherEmployee->full_name);
});
it('can filter employees by status', function () {
$activeUser = User::factory()->active()->create();
$activeEmployee = Employee::factory()->for($activeUser)->create(['full_name' => 'Pegawai Aktif']);
$inactiveUser = User::factory()->inactive()->create();
$inactiveEmployee = Employee::factory()->for($inactiveUser)->create(['full_name' => 'Pegawai Nonaktif']);
Livewire::actingAs($this->user)
->test(Index::class)
->set('status', [\App\Enums\UserStatus::ACTIVE->value])
->assertSee($activeEmployee->full_name)
->assertDontSee($inactiveEmployee->full_name);
});
it('can delete a user', function () {
$userToDelete = User::factory()->create();
Employee::factory()->for($userToDelete)->create();
Livewire::actingAs($this->user)
->test(Index::class)
->call('delete', $userToDelete->id);
$this->assertSoftDeleted('users', ['id' => $userToDelete->id]);
$this->assertSoftDeleted('employees', ['user_id' => $userToDelete->id]);
});
it('can reset user password', function () {
$userToReset = User::factory()->create(['password' => Hash::make('old-password')]);
Livewire::actingAs($this->user)
->test(Index::class)
->call('resetPassword', $userToReset->id);
$userToReset->refresh();
expect(Hash::check(config('myconfig.password_default'), $userToReset->password))->toBeTrue();
});
it('cannot access index page without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
$this->actingAs($this->user)
->get(route('studio.master.user.index'))
->assertForbidden();
});