feat: Add comprehensive feature tests for outlet CRUD operations and refactor outlet index data loading to fetch data directly in the render method.

This commit is contained in:
Yoga Pangestu 2026-01-02 16:37:25 +07:00
parent cc18a1fae5
commit 39c0652e3e
5 changed files with 268 additions and 29 deletions

View File

@ -11,7 +11,6 @@
use App\Traits\Media\WithMediaHandler;
use App\Traits\Utilities\WithRequestNotification;
use Flux\Flux;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Livewire\Attributes\Title;
@ -22,26 +21,10 @@ class Index extends Component
{
use WithAuthorization, WithCloseModal, WithConfirmation, WithMediaHandler, WithRequestNotification, WithToast;
public Collection $outlets;
public string $search = '';
public array $status = [];
public function mount(): void
{
$this->loadOutlets();
}
protected function loadOutlets(): void
{
$this->outlets = Outlet::with(['openingHours', 'facilities'])
->when(! empty($this->search), fn ($query) => $query->where('name', 'like', '%'.$this->search.'%'))
->when(! empty($this->status), fn ($query) => $query->whereIn('status', $this->status))
->latest()
->get();
}
protected function formatOpeningHours(Outlet $outlet): array
{
if (! $outlet->openingHours || $outlet->openingHours->isEmpty()) {
@ -73,31 +56,26 @@ protected function getGalleryImages(Outlet $outlet): array
->toArray();
}
public function updatedSearch(string $value): void
{
$this->search = $value;
$this->loadOutlets();
}
public function updatedStatus(): void
{
$this->loadOutlets();
}
public function delete(Outlet $outlet): void
{
$this->canOrAbort('delete outlet');
$outlet->delete();
$this->toast('Outlet berhasil dihapus.');
$this->loadOutlets();
Flux::modals()->close();
}
public function render(): View
{
$outlets = Outlet::with(['openingHours', 'facilities'])
->when(! empty($this->search), fn ($query) => $query->where('name', 'like', '%'.$this->search.'%'))
->when(! empty($this->status), fn ($query) => $query->whereIn('status', $this->status))
->latest()
->get();
return view('livewire.studio.master.outlet.index', [
'pageTitle' => 'Outlet',
'outlets' => $outlets,
]);
}
}

View File

@ -0,0 +1,96 @@
<?php
use App\Enums\Day;
use App\Enums\OutletStatus;
use App\Livewire\Studio\Master\Outlet\Create;
use App\Models\Outlet;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
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 outlet']);
$role->givePermissionTo('create outlet');
$this->user->assignRole($role);
Storage::fake('public');
});
it('renders the create outlet page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.master.outlet.create'))
->assertOk()
->assertSeeLivewire(Create::class);
});
it('validates required fields', function () {
Livewire::actingAs($this->user)
->test(Create::class)
->call('save')
->assertHasErrors([
'form.name' => 'required',
'form.phone_number' => 'required',
'form.address' => 'required',
'form.landmark' => 'required',
'form.opened_date' => 'required',
'form.featured_image' => 'required',
'form.images' => 'required',
]);
});
it('can store a new outlet with opening hours and facilities', function () {
$featuredImage = UploadedFile::fake()->image('featured.jpg');
$galleryImage = UploadedFile::fake()->image('gallery.jpg');
// Prepare opening hours data
$openingHours = [];
foreach (Day::cases() as $day) {
$openingHours[$day->value] = [
'open_time' => '08:00',
'close_time' => '22:00',
];
}
Livewire::actingAs($this->user)
->test(Create::class)
->set('form.name', 'Outlet Baru')
->set('form.phone_number', '0812 3456 7890')
->set('form.address', 'Jl. Test No. 123')
->set('form.landmark', 'Dekat Monas')
->set('form.maps_url', 'https://goo.gl/maps/abcde')
->set('form.opened_date', '2024-01-01')
->set('form.status', OutletStatus::OPERATIONAL->value)
->set('form.facilities', ['Wifi', 'Parkir'])
->set('form.opening_hours', $openingHours)
// Mocking the data structure expected by handleMediaUpload/uploadMedia
->set('form.featured_image', [['path' => $featuredImage->getPathname()]])
->set('form.images', [['path' => $galleryImage->getPathname()]])
->call('save')
->assertHasNoErrors()
->assertRedirect(route('studio.master.outlet.index', ['notification' => 'Outlet berhasil ditambahkan.']));
$this->assertDatabaseHas('outlets', [
'name' => 'Outlet Baru',
'phone_number' => '0812 3456 7890',
]);
$outlet = Outlet::where('name', 'Outlet Baru')->first();
expect($outlet->openingHours)->toHaveCount(7);
expect($outlet->facilities)->toHaveCount(2);
});
it('cannot create outlet without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Create::class)
->set('form.name', 'Outlet Unauthorized')
->call('save')
->assertForbidden();
});

View File

@ -0,0 +1,60 @@
<?php
use App\Enums\OutletStatus;
use App\Livewire\Studio\Master\Outlet\Edit;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
beforeEach(function () {
$this->setupUser();
$this->outlet = $this->createFullOutlet(['name' => 'Original Outlet']);
$this->user->outlets()->sync([$this->outlet->id]);
$role = Role::firstOrCreate(['name' => 'Owner']);
Permission::firstOrCreate(['name' => 'update outlet']);
$role->givePermissionTo('update outlet');
$this->user->assignRole($role);
});
it('renders the edit outlet page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.master.outlet.edit', $this->outlet))
->assertOk()
->assertSeeLivewire(Edit::class);
});
it('loads existing outlet data correctly', function () {
Livewire::actingAs($this->user)
->test(Edit::class, ['outlet' => $this->outlet])
->assertSet('form.name', $this->outlet->name)
->assertSet('form.phone_number', $this->outlet->phone_number);
});
it('can update an outlet', function () {
Livewire::actingAs($this->user)
->test(Edit::class, ['outlet' => $this->outlet])
->set('form.name', 'Outlet Diperbarui')
->set('form.phone_number', '0812 3456 7890')
->set('form.status', OutletStatus::UNDER_CONSTRUCTION->value)
->call('save')
->assertHasNoErrors()
->assertRedirect(route('studio.master.outlet.index', ['notification' => 'Outlet berhasil diperbarui.']));
$this->assertDatabaseHas('outlets', [
'id' => $this->outlet->id,
'name' => 'Outlet Diperbarui',
'status' => OutletStatus::UNDER_CONSTRUCTION->value,
]);
});
it('cannot update outlet without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Edit::class, ['outlet' => $this->outlet])
->set('form.name', 'Unauthorized Update')
->call('save')
->assertForbidden();
});

View File

@ -0,0 +1,78 @@
<?php
use App\Livewire\Studio\Master\Outlet\Index;
use App\Models\Outlet;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
beforeEach(function () {
$this->setupUser();
// Setup role and permission
$role = Role::firstOrCreate(['name' => 'Owner']);
Permission::firstOrCreate(['name' => 'view outlet']);
Permission::firstOrCreate(['name' => 'delete outlet']);
$role->givePermissionTo(['view outlet', 'delete outlet']);
$this->user->assignRole($role);
});
it('renders the outlet index page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.master.outlet.index'))
->assertOk()
->assertSeeLivewire(Index::class);
});
it('displays list of outlets correctly', function () {
$outlets = Outlet::factory()->count(3)->create();
Livewire::actingAs($this->user)
->test(Index::class)
->assertViewHas('outlets', function ($viewOutlets) {
return $viewOutlets->count() >= 3;
})
->assertSee($outlets->first()->name)
->assertSee($outlets->last()->name);
});
it('can search outlets by name', function () {
$targetOutlet = Outlet::factory()->create(['name' => 'Outlet Spesifik']);
$otherOutlet = Outlet::factory()->create(['name' => 'Toko Biasa']);
Livewire::actingAs($this->user)
->test(Index::class)
->set('search', 'Spesifik')
->assertSee($targetOutlet->name)
->assertDontSee($otherOutlet->name);
});
it('can filter outlets by status', function () {
$operationalOutlet = Outlet::factory()->operational()->create(['name' => 'Outlet Aktif']);
$terminatedOutlet = Outlet::factory()->terminated()->create(['name' => 'Outlet Tutup']);
Livewire::actingAs($this->user)
->test(Index::class)
->set('status', [\App\Enums\OutletStatus::OPERATIONAL->value])
->assertSee($operationalOutlet->name)
->assertDontSee($terminatedOutlet->name);
});
it('can delete an outlet', function () {
$outletToDelete = Outlet::factory()->create();
Livewire::actingAs($this->user)
->test(Index::class)
->call('delete', $outletToDelete->id);
$this->assertSoftDeleted('outlets', ['id' => $outletToDelete->id]);
});
it('cannot access index page without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
$this->actingAs($this->user)
->get(route('studio.master.outlet.index'))
->assertForbidden();
});

View File

@ -60,6 +60,33 @@ public function setupUser(array $overrides = []): void
->for($this->outlet)
->create();
}
public function createFullOutlet(array $overrides = []): \App\Models\Outlet
{
$outlet = \App\Models\Outlet::factory()->create(array_merge([
'landmark' => 'Dekat Patung',
'maps_url' => 'https://goo.gl/maps/test',
], $overrides));
foreach (\App\Enums\Day::cases() as $day) {
\App\Models\OpeningHour::factory()->create([
'outlet_id' => $outlet->id,
'day' => $day->value,
'open_time' => '08:00',
'close_time' => '22:00',
]);
}
$outlet->facilities()->create(['name' => 'Wifi']);
$featured = \Illuminate\Http\UploadedFile::fake()->image('featured.jpg');
$outlet->addMedia($featured->getPathname())->preservingOriginal()->toMediaCollection('featured_image');
$gallery = \Illuminate\Http\UploadedFile::fake()->image('gallery.jpg');
$outlet->addMedia($gallery->getPathname())->preservingOriginal()->toMediaCollection('images');
return $outlet;
}
}
pest()->use(HasUserSetup::class)->in('Feature');