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(); });