diff --git a/database/factories/EmployeeFactory.php b/database/factories/EmployeeFactory.php index 9390d1c..c449696 100644 --- a/database/factories/EmployeeFactory.php +++ b/database/factories/EmployeeFactory.php @@ -24,6 +24,15 @@ public function definition(): array 'birthdate' => $this->faker->dateTimeBetween('-60 years', '-18 years')->format('Y-m-d'), 'hire_date' => $this->faker->date(), 'resign_date' => $this->faker->optional()->date(), + 'gender' => fake()->randomElement([ + Gender::MALE, + Gender::FEMALE, + ]), + 'status' => fake()->randomElement([ + EmploymentStatus::PERMANENT, + EmploymentStatus::CONTRACT, + EmploymentStatus::INTERN, + ]), ]; } diff --git a/database/factories/OutletFactory.php b/database/factories/OutletFactory.php index 31c6136..03fd64b 100644 --- a/database/factories/OutletFactory.php +++ b/database/factories/OutletFactory.php @@ -21,6 +21,11 @@ public function definition(): array 'landmark' => $this->faker->streetName(), 'maps_url' => 'https://goo.gl/maps/'.$this->faker->regexify('[A-Za-z0-9]{6,10}'), 'opened_date' => $this->faker->date(), + 'status' => fake()->randomElement([ + OutletStatus::OPERATIONAL, + OutletStatus::UNDER_CONSTRUCTION, + OutletStatus::TERMINATED, + ]), ]; } diff --git a/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php b/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php index 614211f..e26802e 100644 --- a/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php +++ b/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php @@ -3,6 +3,7 @@ use App\Enums\Day; use App\Livewire\Studio\Master\Outlet\Create; use App\Livewire\Studio\Master\Outlet\Index; +use App\Models\Employee; use App\Models\Facility; use App\Models\Outlet; use App\Models\User; @@ -13,11 +14,21 @@ use Illuminate\Support\Str; use Livewire\Livewire; use Spatie\Permission\Models\Permission; +use Spatie\Permission\Models\Role; uses(RefreshDatabase::class); beforeEach(function () { - $this->user = User::factory()->create(); + $roles = collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer']) + ->map(fn ($role) => Role::create(['name' => $role])); + + $this->user = User::factory() + ->active() + ->has(Employee::factory()) + ->has(Outlet::factory()->count(5)) + ->create(); + + $this->user->roles()->attach($roles->pluck('id')); }); function makeLivewireFile(string $name) @@ -133,3 +144,113 @@ function mountCreateComponent(User $user) expect($media->getPath())->toBeFile(); } }); + +it('validates required fields', function () { + $permission = Permission::create(['name' => 'create outlet']); + $this->user->givePermissionTo($permission); + + mountCreateComponent($this->user) + ->call('save') + ->assertHasErrors([ + 'form.name', + 'form.phone_number', + 'form.address', + 'form.landmark', + 'form.opening_hours', + 'form.facilities', + 'form.opened_date', + 'form.featured_image', + 'form.images', + ]); +}); + +it('validates name field max length', function () { + $permission = Permission::create(['name' => 'create outlet']); + $this->user->givePermissionTo($permission); + + mountCreateComponent($this->user) + ->set('form.name', str_repeat('a', 101)) + ->call('save') + ->assertHasErrors(['form.name']); +}); + +it('validates facilities minimum count', function () { + $permission = Permission::create(['name' => 'create outlet']); + $this->user->givePermissionTo($permission); + + mountCreateComponent($this->user) + ->set('form.facilities', []) + ->call('save') + ->assertHasErrors(['form.facilities']); +}); + +it('validates featured image max count', function () { + $permission = Permission::create(['name' => 'create outlet']); + $this->user->givePermissionTo($permission); + + $featuredImages = [ + makeLivewireFile('featured1.jpg'), + makeLivewireFile('featured2.jpg'), + ]; + + mountCreateComponent($this->user) + ->set('form.featured_image', $featuredImages) + ->call('save') + ->assertHasErrors(['form.featured_image']); +}); + +it('validates images max count', function () { + $permission = Permission::create(['name' => 'create outlet']); + $this->user->givePermissionTo($permission); + + $images = []; + for ($i = 1; $i <= 6; $i++) { + $images[] = makeLivewireFile("image{$i}.jpg"); + } + + mountCreateComponent($this->user) + ->set('form.images', $images) + ->call('save') + ->assertHasErrors(['form.images']); +}); + +it('has default facilities value', function () { + $component = mountCreateComponent($this->user); + + expect($component->form->facilities)->toBe(['Parkir']); +}); + +it('creates outlet with opening hours for all days', function () { + $permission = Permission::create(['name' => 'create outlet']); + $this->user->givePermissionTo($permission); + + $outlet = Outlet::factory()->operational()->raw(); + $featuredImage = makeLivewireFile('featured.jpg'); + $images = [makeLivewireFile('image1.jpg')]; + + $openingHours = collect(Day::cases()) + ->mapWithKeys(fn ($day) => [ + $day->value => [ + 'open_time' => '09:00', + 'close_time' => '21:00', + ], + ]) + ->toArray(); + + mountCreateComponent($this->user) + ->set('form.name', $outlet['name']) + ->set('form.phone_number', $outlet['phone_number']) + ->set('form.address', $outlet['address']) + ->set('form.landmark', $outlet['landmark']) + ->set('form.opened_date', $outlet['opened_date']) + ->set('form.status', $outlet['status']->value) + ->set('form.opening_hours', $openingHours) + ->set('form.facilities', ['Parkir']) + ->set('form.featured_image', [$featuredImage]) + ->set('form.images', $images) + ->call('save') + ->assertHasNoErrors(); + + $createdOutlet = Outlet::first(); + expect($createdOutlet->openingHours)->toHaveCount(count(Day::cases())); +}); diff --git a/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php b/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php index b62b349..f68b916 100644 --- a/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php +++ b/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php @@ -2,17 +2,28 @@ use App\Enums\OutletStatus; use App\Livewire\Studio\Master\Outlet\Index; +use App\Models\Employee; use App\Models\Facility; use App\Models\OpeningHour; use App\Models\Outlet; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\Livewire; +use Spatie\Permission\Models\Role; uses(RefreshDatabase::class); beforeEach(function () { - $this->user = User::factory()->create(); + $roles = collect(['Developer', 'Owner', 'Leader', 'Admin', 'Partner', 'Customer']) + ->map(fn ($role) => Role::create(['name' => $role])); + + $this->user = User::factory() + ->active() + ->has(Employee::factory()) + // ->has(Outlet::factory()->count(5)) + ->create(); + + $this->user->roles()->attach($roles->pluck('id')); }); function createOutlet(array $attributes = []) @@ -41,8 +52,12 @@ function mountIndexComponent(User $user) $outlet = createOutlet(); mountIndexComponent($this->user) - ->assertSet('outlets.0.name', $outlet->name) - ->assertSet('outlets.0.phone_number', $outlet->phone_number); + ->assertViewHas('outlets', function ($outlets) use ($outlet) { + return collect($outlets)->contains( + fn ($o) => $o['name'] === $outlet->name && + $o['phone_number'] === $outlet->phone_number + ); + }); }); it('opens image modal when openImage is called', function () { @@ -74,3 +89,98 @@ function mountIndexComponent(User $user) $this->assertSoftDeleted('outlets', ['id' => $outlet->id]); }); + +it('filters outlets by search query', function () { + $outlet1 = createOutlet(['name' => 'Outlet Jakarta']); + $outlet2 = createOutlet(['name' => 'Outlet Bandung']); + $outlet3 = createOutlet(['name' => 'Toko Surabaya']); + + $component = mountIndexComponent($this->user); + + expect(count($component->get('outlets')))->toBe(3); + + $component->set('search', 'Jakarta') + ->assertSet('search', 'Jakarta'); + + expect(count($component->get('outlets')))->toBe(1); + expect($component->get('outlets.0.name'))->toBe('Outlet Jakarta'); + + $component->set('search', 'Outlet') + ->assertSet('search', 'Outlet'); + + expect(count($component->get('outlets')))->toBe(2); +}); + +it('filters outlets by status', function () { + $outlet1 = createOutlet(['status' => OutletStatus::OPERATIONAL]); + $outlet2 = createOutlet(['status' => OutletStatus::UNDER_CONSTRUCTION]); + $outlet3 = createOutlet(['status' => OutletStatus::TERMINATED]); + + $component = mountIndexComponent($this->user); + + expect(count($component->get('outlets')))->toBe(3); + + $component->set('status', [OutletStatus::OPERATIONAL->value]) + ->assertSet('status', [OutletStatus::OPERATIONAL->value]); + + expect(count($component->get('outlets')))->toBe(1); + expect($component->get('outlets.0.name'))->toBe($outlet1->name); + + $component->set('status', [ + OutletStatus::OPERATIONAL->value, + OutletStatus::UNDER_CONSTRUCTION->value, + ]); + + expect(count($component->get('outlets')))->toBe(2); +}); + +it('filters outlets by both search and status', function () { + $outlet1 = createOutlet(['name' => 'Outlet A', 'status' => OutletStatus::OPERATIONAL]); + $outlet2 = createOutlet(['name' => 'Outlet B', 'status' => OutletStatus::OPERATIONAL]); + $outlet3 = createOutlet(['name' => 'Outlet C', 'status' => OutletStatus::UNDER_CONSTRUCTION]); + + $component = mountIndexComponent($this->user) + ->set('search', 'Outlet') + ->set('status', [OutletStatus::OPERATIONAL->value]); + + expect(count($component->get('outlets')))->toBe(2); +}); + +it('loads outlet data with all required fields', function () { + $outlet = createOutlet(); + + $component = mountIndexComponent($this->user); + $outletData = $component->get('outlets.0'); + + expect($outletData)->toHaveKeys([ + 'hash', + 'name', + 'phone_number', + 'landmark', + 'address', + 'status', + 'opening_hours', + 'facilities', + 'opened_date', + 'opened_ago', + 'closed_date', + 'closed_ago', + 'image', + 'images', + ]); + + expect($outletData['status'])->toHaveKeys(['label', 'color']); + expect($outletData['name'])->toBe($outlet->name); + expect($outletData['phone_number'])->toBe($outlet->phone_number); +}); + +it('loads outlets ordered by latest', function () { + $outlet1 = createOutlet(['name' => 'First']); + sleep(1); + $outlet2 = createOutlet(['name' => 'Second']); + + $component = mountIndexComponent($this->user); + + expect($component->get('outlets.0.name'))->toBe('Second'); + expect($component->get('outlets.1.name'))->toBe('First'); +});