644 lines
21 KiB
PHP
644 lines
21 KiB
PHP
<?php
|
|
|
|
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);
|
|
|
|
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('initializes form properties on mount', function () {
|
|
$component = mountCustomerComponent($this->user);
|
|
|
|
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('loads stats on mount', function () {
|
|
// Create customers without users
|
|
Customer::factory()->count(3)->create(['user_id' => null]);
|
|
|
|
// 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'])
|
|
->call('create')
|
|
->assertHasNoErrors()
|
|
->assertDispatched('refreshDatatable');
|
|
|
|
$this->assertDatabaseHas('customers', [
|
|
'name' => $data['name'],
|
|
'phone_number' => $data['phone_number'],
|
|
'gender' => Gender::MALE->value,
|
|
]);
|
|
});
|
|
|
|
it('can create a customer without phone number', function () {
|
|
$data = [
|
|
'name' => 'Jane Doe',
|
|
'phone_number' => null,
|
|
'gender' => Gender::FEMALE->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'])
|
|
->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('Jane Doe');
|
|
expect($customer->phone_number)->toBe('0812 9999 8888');
|
|
expect($customer->gender)->toBe(Gender::FEMALE);
|
|
});
|
|
|
|
it('can update customer phone number to null', function () {
|
|
$customer = Customer::factory()->create([
|
|
'phone_number' => '0812 3456 7890',
|
|
]);
|
|
|
|
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(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();
|
|
});
|