feat(customer): implementasi testing

This commit is contained in:
Yoga Pangestu 2025-12-08 13:11:36 +07:00
parent a4dfdfdc6a
commit 43116fdec2

View File

@ -1,30 +1,211 @@
<?php
use App\Livewire\Studio\Loyalty\Customer;
use App\Models\Customer as CustomerModel;
use App\Enums\Gender;
use App\Livewire\Studio\Loyalty\Customer as CustomerComponent;
use App\Models\Customer;
use App\Models\Employee;
use App\Models\Order;
use App\Models\Outlet;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
use Livewire\Livewire;
use Spatie\Permission\Models\Role;
uses(RefreshDatabase::class);
it('renders successfully', function () {
Livewire::test(Customer::class)
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
$this->user = User::factory()
->active()
->has(Employee::factory())
->create();
$this->ownerRole = Role::where('name', 'Owner')->first();
$this->user->roles()->attach($this->ownerRole->id);
});
function mountCustomerComponent(User $user)
{
return Livewire::actingAs($user)->test(CustomerComponent::class);
}
/*
|--------------------------------------------------------------------------
| Component Rendering & Mount
|--------------------------------------------------------------------------
*/
it('renders page successfully', function () {
mountCustomerComponent($this->user)
->assertViewIs('livewire.studio.loyalty.customers')
->assertViewHas('pageTitle', 'Customer');
});
it('displays all customers', function () {
CustomerModel::factory()->count(10)->create();
it('initializes form properties on mount', function () {
$component = mountCustomerComponent($this->user);
$customer = CustomerModel::first();
Livewire::test(Customer::class)->assertSee($customer->name);
expect($component->method)->toBe('create');
expect($component->modalTitle)->toBe('');
expect($component->form->name)->toBe('');
expect($component->form->phone_number)->toBeNull();
expect($component->form->gender)->toBe('');
});
it('can create a customer', function () {
$data = CustomerModel::factory()->raw();
it('loads stats on mount', function () {
// Create customers without users
Customer::factory()->count(3)->create(['user_id' => null]);
Livewire::test(Customer::class)
// Create customers with users
$user1 = User::factory()->create();
$user2 = User::factory()->create();
Customer::factory()->create(['user_id' => $user1->id]);
Customer::factory()->create(['user_id' => $user2->id]);
$component = mountCustomerComponent($this->user);
expect($component->stats)->toBeArray();
expect($component->stats)->toHaveCount(3);
expect($component->stats[0]['title'])->toBe('Total Customer');
expect($component->stats[1]['title'])->toBe('Total Member');
expect($component->stats[2]['title'])->toBe('Top Member');
});
it('calculates total customer count correctly', function () {
Customer::factory()->count(5)->create(['user_id' => null]);
Customer::factory()->count(3)->create(['user_id' => User::factory()->create()->id]);
$component = mountCustomerComponent($this->user);
expect($component->stats[0]['value'])->toBe(5);
});
it('calculates total member count correctly', function () {
Customer::factory()->count(2)->create(['user_id' => null]);
$user1 = User::factory()->create();
$user2 = User::factory()->create();
Customer::factory()->create(['user_id' => $user1->id]);
Customer::factory()->create(['user_id' => $user2->id]);
$component = mountCustomerComponent($this->user);
expect($component->stats[1]['value'])->toBe(2);
});
it('displays top member name correctly', function () {
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$customer1 = Customer::factory()->create(['user_id' => $user1->id, 'name' => 'Top Customer']);
$customer2 = Customer::factory()->create(['user_id' => $user2->id, 'name' => 'Regular Customer']);
// Create more orders for customer1
$outlet = Outlet::factory()->create();
Order::factory()->count(5)->create(['customer_id' => $customer1->id, 'user_id' => $user1->id, 'outlet_id' => $outlet->id]);
Order::factory()->count(2)->create(['customer_id' => $customer2->id, 'user_id' => $user2->id, 'outlet_id' => $outlet->id]);
$component = mountCustomerComponent($this->user);
expect($component->stats[2]['value'])->toBe('Top Customer');
});
it('displays null when no members exist', function () {
Customer::factory()->count(3)->create(['user_id' => null]);
$component = mountCustomerComponent($this->user);
expect($component->stats[2]['value'])->toBeNull();
});
it('displays null when members have no orders', function () {
$user = User::factory()->create();
Customer::factory()->create(['user_id' => $user->id]);
$component = mountCustomerComponent($this->user);
expect($component->stats[2]['value'])->toBeNull();
});
/*
|--------------------------------------------------------------------------
| Modal Management
|--------------------------------------------------------------------------
*/
it('opens modal with create method', function () {
$component = mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer');
expect($component->method)->toBe('create');
expect($component->modalTitle)->toBe('Tambah Customer');
});
it('opens modal with update method', function () {
$customer = Customer::factory()->create();
$component = mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash);
expect($component->method)->toBe('update');
expect($component->modalTitle)->toBe('Edit Customer');
});
it('sets form data when opening update modal', function () {
$customer = Customer::factory()->create([
'name' => 'John Doe',
'phone_number' => '0812 3456 7890',
'gender' => Gender::MALE->value,
]);
$component = mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash);
expect($component->form->name)->toBe('John Doe');
expect($component->form->phone_number)->toBe('0812 3456 7890');
expect($component->form->gender)->toEqual(Gender::MALE->value);
});
it('resets validation and error bag when opening modal', function () {
$component = mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', '')
->call('create')
->assertHasErrors(['form.name']);
$component->call('openModal', 'create', 'Tambah Customer')
->assertHasNoErrors();
});
/*
|--------------------------------------------------------------------------
| Create Customer
|--------------------------------------------------------------------------
*/
it('prevents create when user is unauthorized', function () {
$this->ownerRole->revokePermissionTo('create customer');
Gate::define('create customer', fn () => false);
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'John Doe')
->set('form.gender', Gender::MALE->value)
->call('create')
->assertForbidden();
});
it('can create a customer when authorized', function () {
$data = [
'name' => 'John Doe',
'phone_number' => '0812 3456 7890',
'gender' => Gender::MALE->value,
];
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', $data['name'])
->set('form.phone_number', $data['phone_number'])
->set('form.gender', $data['gender'])
@ -32,38 +213,431 @@
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
expect(CustomerModel::count())->toBe(1);
expect(CustomerModel::first()->name)->toBe($data['name']);
$this->assertDatabaseHas('customers', [
'name' => $data['name'],
'phone_number' => $data['phone_number'],
'gender' => Gender::MALE->value,
]);
});
it('can update a customer', function () {
$customer = CustomerModel::factory()->create();
it('can create a customer without phone number', function () {
$data = [
'name' => 'Jane Doe',
'phone_number' => null,
'gender' => Gender::FEMALE->value,
];
$data = CustomerModel::factory()->raw();
Livewire::test(Customer::class)
->call('openModal', 'update', 'Edit Customer', $customer->id)
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', $data['name'])
->set('form.phone_number', $data['phone_number'])
->set('form.gender', $data['gender'])
->call('create')
->assertHasNoErrors();
$this->assertDatabaseHas('customers', [
'name' => $data['name'],
'phone_number' => null,
'gender' => Gender::FEMALE->value,
]);
});
it('can create a customer with user_id', function () {
$user = User::factory()->create();
$data = [
'name' => 'Member Customer',
'gender' => Gender::MALE->value,
];
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', $data['name'])
->set('form.gender', $data['gender'])
->set('form.user_id', $user->id)
->call('create')
->assertHasNoErrors();
$this->assertDatabaseHas('customers', [
'name' => $data['name'],
'user_id' => $user->id,
]);
});
it('validates required fields when creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->call('create')
->assertHasErrors([
'form.name',
'form.gender',
]);
});
it('validates name field max length when creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', str_repeat('a', 51))
->set('form.gender', Gender::MALE->value)
->call('create')
->assertHasErrors(['form.name']);
});
it('validates gender field is required when creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'John Doe')
->set('form.gender', '')
->call('create')
->assertHasErrors(['form.gender']);
});
it('validates gender field is valid enum when creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'John Doe')
->set('form.gender', 'invalid')
->call('create')
->assertHasErrors(['form.gender']);
});
it('validates phone number format when creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'John Doe')
->set('form.phone_number', 'invalid-phone')
->set('form.gender', Gender::MALE->value)
->call('create')
->assertHasErrors(['form.phone_number']);
});
it('accepts valid phone number format when creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'John Doe')
->set('form.phone_number', '0812 3456 7890')
->set('form.gender', Gender::MALE->value)
->call('create')
->assertHasNoErrors();
});
it('dispatches refreshDatatable event after creating customer', function () {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'John Doe')
->set('form.gender', Gender::MALE->value)
->call('create')
->assertDispatched('refreshDatatable');
});
/*
|--------------------------------------------------------------------------
| Update Customer
|--------------------------------------------------------------------------
*/
it('prevents update when user is unauthorized', function () {
$this->ownerRole->revokePermissionTo('update customer');
Gate::define('update customer', fn () => false);
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->set('form.name', 'Updated Name')
->call('update')
->assertForbidden();
});
it('can update a customer when authorized', function () {
$customer = Customer::factory()->create([
'name' => 'John Doe',
'phone_number' => '0812 3456 7890',
'gender' => Gender::MALE,
]);
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->assertSet('form.name', 'John Doe')
->assertSet('form.phone_number', '0812 3456 7890')
->assertSet('form.gender', Gender::MALE->value)
->set('form.name', 'Jane Doe')
->set('form.phone_number', '0812 9999 8888')
->set('form.gender', Gender::FEMALE->value)
->call('update')
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
$customer->refresh();
expect($customer->name)->toBe($data['name']);
expect($customer->phone_number)->toBe($data['phone_number']);
expect($customer->gender)->toBe($data['gender']);
expect($customer->name)->toBe('Jane Doe');
expect($customer->phone_number)->toBe('0812 9999 8888');
expect($customer->gender)->toBe(Gender::FEMALE);
});
it('can delete an customer', function () {
$customer = CustomerModel::factory()->create();
it('can update customer phone number to null', function () {
$customer = Customer::factory()->create([
'phone_number' => '0812 3456 7890',
]);
Livewire::test(Customer::class)
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->set('form.phone_number', null)
->call('update')
->assertHasNoErrors();
$customer->refresh();
expect($customer->phone_number)->toBeNull();
});
it('validates required fields when updating customer', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->set('form.name', '')
->set('form.gender', '')
->call('update')
->assertHasErrors([
'form.name',
'form.gender',
]);
});
it('validates name field max length when updating customer', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->set('form.name', str_repeat('a', 51))
->call('update')
->assertHasErrors(['form.name']);
});
it('validates phone number format when updating customer', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->set('form.phone_number', 'invalid-phone')
->call('update')
->assertHasErrors(['form.phone_number']);
});
it('dispatches refreshDatatable event after updating customer', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->set('form.name', 'Updated Name')
->call('update')
->assertDispatched('refreshDatatable');
});
/*
|--------------------------------------------------------------------------
| Delete Customer
|--------------------------------------------------------------------------
*/
it('can delete a customer', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('delete', $customer)
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
expect(CustomerModel::count())->toBe(0);
expect(Customer::count())->toBe(0);
$this->assertSoftDeleted('customers', ['id' => $customer->id]);
});
it('soft deletes customer when deleting', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('delete', $customer);
$customer->refresh();
expect($customer->trashed())->toBeTrue();
});
it('dispatches refreshDatatable event after deleting customer', function () {
$customer = Customer::factory()->create();
mountCustomerComponent($this->user)
->call('delete', $customer)
->assertDispatched('refreshDatatable');
});
it('can delete customer with orders', function () {
$outlet = Outlet::factory()->create();
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id]);
Order::factory()->count(3)->create([
'customer_id' => $customer->id,
'user_id' => $user->id,
'outlet_id' => $outlet->id,
]);
mountCustomerComponent($this->user)
->call('delete', $customer);
expect($customer->trashed())->toBeTrue();
// Orders should still exist (cascade delete might be configured differently)
expect(Order::count())->toBe(3);
});
/*
|--------------------------------------------------------------------------
| Stats Recalculation
|--------------------------------------------------------------------------
*/
it('recalculates stats after creating customer', function () {
$initialStats = mountCustomerComponent($this->user)->stats;
$initialCustomerCount = $initialStats[0]['value'];
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'New Customer')
->set('form.gender', Gender::MALE->value)
->call('create');
$newComponent = mountCustomerComponent($this->user);
expect($newComponent->stats[0]['value'])->toBe($initialCustomerCount + 1);
});
it('recalculates stats after creating member customer', function () {
$user = User::factory()->create();
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'New Member')
->set('form.gender', Gender::MALE->value)
->set('form.user_id', $user->id)
->call('create');
$newComponent = mountCustomerComponent($this->user);
expect($newComponent->stats[1]['value'])->toBeGreaterThan(0);
});
it('recalculates top member after creating orders', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id, 'name' => 'Top Member']);
$outlet = Outlet::factory()->create();
Order::factory()->count(5)->create([
'customer_id' => $customer->id,
'user_id' => $user->id,
'outlet_id' => $outlet->id,
]);
$component = mountCustomerComponent($this->user);
expect($component->stats[2]['value'])->toBe('Top Member');
});
/*
|--------------------------------------------------------------------------
| Edge Cases
|--------------------------------------------------------------------------
*/
it('handles customer with very long name at max length', function () {
$longName = str_repeat('a', 50);
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', $longName)
->set('form.gender', Gender::MALE->value)
->call('create')
->assertHasNoErrors();
$this->assertDatabaseHas('customers', ['name' => $longName]);
});
it('handles customer with all gender types', function () {
foreach (Gender::cases() as $gender) {
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', "Customer {$gender->name}")
->set('form.gender', $gender->value)
->call('create')
->assertHasNoErrors();
}
expect(Customer::where('gender', Gender::MALE->value)->count())->toBeGreaterThan(0);
expect(Customer::where('gender', Gender::FEMALE->value)->count())->toBeGreaterThan(0);
});
it('handles updating customer with same data', function () {
$customer = Customer::factory()->create([
'name' => 'John Doe',
'phone_number' => '0812 3456 7890',
'gender' => Gender::MALE,
]);
mountCustomerComponent($this->user)
->call('openModal', 'update', 'Edit Customer', $customer->hash)
->call('update')
->assertHasNoErrors();
$customer->refresh();
expect($customer->name)->toBe('John Doe');
});
it('handles customer with special characters in name', function () {
$specialName = "O'Brien & Co. - Test";
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', $specialName)
->set('form.gender', Gender::MALE->value)
->call('create')
->assertHasNoErrors();
$this->assertDatabaseHas('customers', ['name' => $specialName]);
});
it('handles multiple customers with same phone number', function () {
$phoneNumber = '0812 3456 7890';
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'Customer 1')
->set('form.phone_number', $phoneNumber)
->set('form.gender', Gender::MALE->value)
->call('create')
->assertHasNoErrors();
mountCustomerComponent($this->user)
->call('openModal', 'create', 'Tambah Customer')
->set('form.name', 'Customer 2')
->set('form.phone_number', $phoneNumber)
->set('form.gender', Gender::FEMALE->value)
->call('create')
->assertHasNoErrors();
expect(Customer::where('phone_number', $phoneNumber)->count())->toBe(2);
});
it('handles empty stats when no customers exist', function () {
$component = mountCustomerComponent($this->user);
expect($component->stats[0]['value'])->toBe(0);
expect($component->stats[1]['value'])->toBe(0);
expect($component->stats[2]['value'])->toBeNull();
});
it('handles customer deletion when customer does not exist', function () {
$customer = Customer::factory()->create();
$customerId = $customer->id;
$customer->delete();
// Try to delete already deleted customer
$deletedCustomer = Customer::withTrashed()->find($customerId);
mountCustomerComponent($this->user)
->call('delete', $deletedCustomer)
->assertHasNoErrors();
expect(Customer::withTrashed()->find($customerId)->trashed())->toBeTrue();
});