refactor(outlet): menyesuaikan unit test dari segi penamaan, logika bisnis, clean code dan lainnya

-menghapus key closed_date di factory
-salah menulis relasi di model
-antisipasi error ketika image null atau array kosong di index
-memindahkan posisi toast menjadi di atas redirect
This commit is contained in:
Yoga Pangestu 2025-10-14 21:10:22 +07:00
parent 77bce6f0eb
commit 5a49bc8ba4
7 changed files with 225 additions and 124 deletions

View File

@ -34,9 +34,9 @@ public function save()
$this->form->update();
$this->redirectRoute('studio.master.outlet.index', navigate: true);
$this->toast('Outlet berhasil diperbarui.');
$this->redirectRoute('studio.master.outlet.index', navigate: true);
}
public function render()

View File

@ -51,8 +51,11 @@ public function mount()
'closed_ago' => $outlet->closed_date ? timeAgo($outlet->closed_date) : null,
'edit_route' => route('studio.master.outlet.edit', $outlet->hash),
'delete_route' => route('studio.master.outlet.delete', $outlet->hash),
'image' => $this->mapMediaCollection($outlet->getMedia('featured_image'))[0]['temporaryUrl'],
'images' => collect($this->mapMediaCollection($outlet->getMedia('images')))->map(fn ($image) => $image['temporaryUrl']),
'image' => data_get($this->mapMediaCollection($outlet->getMedia('featured_image'))[0] ?? null, 'temporaryUrl', asset('images/placeholder.jpg')),
'images' => collect($this->mapMediaCollection($outlet->getMedia('images')))
->pluck('temporaryUrl')
->filter()
->values(),
])
->toArray();
}
@ -68,8 +71,6 @@ public function delete(Outlet $outlet)
{
$outlet->delete();
$this->dispatch('refreshDatatable');
$this->toast('Outlet berhasil dihapus.');
Flux::modals()->close();

View File

@ -21,7 +21,7 @@ class Outlet extends Model implements HasMedia
protected $guarded = ['id'];
protected $cascadeDeletes = ['openingHours', 'facilities', 'vouchers', 'perfumes', 'products', 'bottles', 'expenses', 'users5'];
protected $cascadeDeletes = ['openingHours', 'facilities', 'vouchers', 'perfumes', 'products', 'bottles', 'expenses', 'users'];
protected function casts(): array
{

View File

@ -21,7 +21,6 @@ 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(),
'closed_date' => $this->faker->optional()->date(),
];
}

View File

@ -5,68 +5,103 @@
use App\Livewire\Studio\Master\Outlet\Index;
use App\Models\Facility;
use App\Models\Outlet;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
uses(RefreshDatabase::class);
it('renders successfully', function () {
Livewire::test(Create::class)
beforeEach(function () {
$this->user = User::factory()->create();
});
function makeLivewireFile(string $name)
{
$file = UploadedFile::fake()->image($name, 100, 100);
$tmpId = Str::random(32);
$filename = $tmpId.'-'.base64_encode($file->getClientOriginalName()).'.'.$file->getClientOriginalExtension();
$path = "livewire-tmp/{$filename}";
Storage::disk('local')->putFileAs('livewire-tmp', $file, $filename);
return [
'tmpFilename' => $filename,
'name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => storage_path("app/private/{$path}"),
'temporaryUrl' => url("livewire/preview-file/{$filename}"),
'size' => $file->getSize(),
];
}
function mountCreateComponent(User $user)
{
return Livewire::actingAs($user)->test(Create::class);
}
it('renders page successfully', function () {
mountCreateComponent($this->user)
->assertViewIs('livewire.studio.master.outlet.form')
->assertViewHas('pageTitle', 'Tambah Outlet');
});
it('can create an outlet', function () {
it('mounts days and form opening_hours correctly', function () {
$component = mountCreateComponent($this->user);
$days = Day::cases();
expect($component->days)->toHaveCount(count($days));
foreach ($days as $day) {
expect(array_key_exists($day->value, $component->form->opening_hours))->toBeTrue();
expect($component->form->opening_hours[$day->value])->toBe(['open_time' => null, 'close_time' => null]);
}
});
it('prevents create when user is unauthorized', function () {
Gate::define('create outlet', fn () => false);
mountCreateComponent($this->user)
->call('save')
->assertForbidden();
});
it('saves outlet successfully when authorized', function () {
$permission = Permission::create(['name' => 'create outlet']);
$this->user->givePermissionTo($permission);
$outlet = Outlet::factory()->operational()->raw();
$facilities = Facility::factory()->count(5)->raw();
$makeLivewireFile = function ($name) {
$file = UploadedFile::fake()->image($name, 100, 100);
$featuredImage = makeLivewireFile('featured.jpg');
$images = [];
$tmpId = Str::random(32);
$filename = $tmpId.'-'.base64_encode($file->getClientOriginalName()).'.'.$file->getClientOriginalExtension();
$path = "livewire-tmp/{$filename}";
for ($i = 1; $i <= 5; $i++) {
$images[] = makeLivewireFile("image{$i}.jpg");
}
Storage::disk('local')->putFileAs('livewire-tmp', $file, $filename);
$openingHours = collect(Day::cases())
->mapWithKeys(fn ($day) => [
$day->value => [
'day' => $day->value,
'open_time' => fake()->time('H:i'),
'close_time' => fake()->time('H:i'),
],
])
->toArray();
return [
'tmpFilename' => $filename,
'name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => storage_path("app/private/{$path}"),
'temporaryUrl' => url("livewire/preview-file/{$filename}"),
'size' => $file->getSize(),
];
};
$data = array_merge($outlet, [
'opening_hours' => $openingHours,
'facilities' => collect($facilities)->pluck('name')->toArray(),
'featured_image' => [$featuredImage],
'images' => $images,
]);
$featuredImage = $makeLivewireFile('featured.jpg');
$images = [
$makeLivewireFile('image1.jpg'),
$makeLivewireFile('image2.png'),
];
$data = array_merge(
$outlet,
[
'opening_hours' => collect(Day::cases())
->mapWithKeys(fn ($day) => [
$day->value => [
'day' => $day->value,
'open_time' => fake()->time('H:i'),
'close_time' => fake()->time('H:i'),
],
])
->toArray(),
'facilities' => collect($facilities)->pluck('name')->toArray(),
'featured_image' => [$featuredImage],
'images' => $images,
]
);
Livewire::test(Create::class)
mountCreateComponent($this->user)
->set('form.name', $data['name'])
->set('form.phone_number', $data['phone_number'])
->set('form.address', $data['address'])
@ -80,7 +115,6 @@
->set('form.images', $data['images'])
->call('save')
->assertHasNoErrors()
->assertDispatched('refreshDatatable')
->assertRedirect(Index::class);
$outlet = Outlet::first();
@ -92,10 +126,9 @@
]);
$this->assertCount(1, $outlet->getMedia('featured_image'));
$this->assertCount(2, $outlet->getMedia('images'));
$this->assertCount(5, $outlet->getMedia('images'));
expect($outlet->getFirstMedia('featured_image')->getPath())->toBeFile();
foreach ($outlet->getMedia('images') as $media) {
expect($media->getPath())->toBeFile();
}

View File

@ -5,70 +5,102 @@
use App\Livewire\Studio\Master\Outlet\Index;
use App\Models\Facility;
use App\Models\Outlet;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
uses(RefreshDatabase::class);
it('renders successfully', function () {
beforeEach(function () {
$this->user = User::factory()->create();
});
function makeLivewireFile(string $name)
{
$file = UploadedFile::fake()->image($name, 100, 100);
$tmpId = Str::random(32);
$filename = $tmpId.'-'.base64_encode($file->getClientOriginalName()).'.'.$file->getClientOriginalExtension();
$path = "livewire-tmp/{$filename}";
Storage::disk('local')->putFileAs('livewire-tmp', $file, $filename);
return [
'tmpFilename' => $filename,
'name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => storage_path("app/private/{$path}"),
'temporaryUrl' => url("livewire/preview-file/{$filename}"),
'size' => $file->getSize(),
];
}
function mountEditComponent(User $user, Outlet $outlet)
{
return Livewire::actingAs($user)->test(Edit::class, ['outlet' => $outlet]);
}
it('renders page successfully', function () {
$outlet = Outlet::factory()->operational()->create();
Livewire::test(Edit::class, ['outlet' => $outlet])
mountEditComponent($this->user, $outlet)
->assertViewIs('livewire.studio.master.outlet.form')
->assertViewHas('pageTitle', 'Ubah Outlet');
});
it('can update an outlet', function () {
it('mounts days and sets form with outlet', function () {
$outlet = Outlet::factory()->operational()->create();
$component = mountEditComponent($this->user, $outlet);
$days = Day::cases();
expect($component->days)->toHaveCount(count($days));
expect($component->form->outlet->id)->toBe($outlet->id);
});
it('prevents update when user is unauthorized', function () {
$outlet = Outlet::factory()->operational()->create();
Gate::define('update outlet', fn () => false);
mountEditComponent($this->user, $outlet)
->call('save')
->assertForbidden();
});
it('updates outlet successfully when authorized', function () {
$permission = Permission::create(['name' => 'update outlet']);
$this->user->givePermissionTo($permission);
$outletModel = Outlet::factory()->operational()->create(); // model instance
$outletData = Outlet::factory()->operational()->raw(); // data for update
$facilities = Facility::factory()->count(5)->raw();
$makeLivewireFile = function ($name) {
$file = UploadedFile::fake()->image($name, 100, 100);
$featuredImage = makeLivewireFile('featured.jpg');
for ($i = 1; $i <= 5; $i++) {
$images[] = makeLivewireFile("image{$i}.jpg");
}
$tmpId = Str::random(32);
$filename = $tmpId.'-'.base64_encode($file->getClientOriginalName()).'.'.$file->getClientOriginalExtension();
$path = "livewire-tmp/{$filename}";
$openingHours = collect(Day::cases())
->mapWithKeys(fn ($day) => [
$day->value => [
'day' => $day->value,
'open_time' => fake()->time('H:i'),
'close_time' => fake()->time('H:i'),
],
])
->toArray();
Storage::disk('local')->putFileAs('livewire-tmp', $file, $filename);
$data = array_merge($outletData, [
'opening_hours' => $openingHours,
'facilities' => collect($facilities)->pluck('name')->toArray(),
'featured_image' => [$featuredImage],
'images' => $images,
]);
return [
'tmpFilename' => $filename,
'name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => storage_path("app/private/{$path}"),
'temporaryUrl' => url("livewire/preview-file/{$filename}"),
'size' => $file->getSize(),
];
};
$featuredImage = $makeLivewireFile('featured.jpg');
$images = [
$makeLivewireFile('image1.jpg'),
$makeLivewireFile('image2.png'),
];
$data = array_merge(
Outlet::factory()->operational()->raw(),
[
'opening_hours' => collect(Day::cases())
->mapWithKeys(fn ($day) => [
$day->value => [
'day' => $day->value,
'open_time' => fake()->time('H:i'),
'close_time' => fake()->time('H:i'),
],
])
->toArray(),
'facilities' => collect($facilities)->pluck('name')->toArray(),
'featured_image' => [$featuredImage],
'images' => $images,
]
);
Livewire::test(Edit::class, ['outlet' => $outlet])
mountEditComponent($this->user, $outletModel)
->set('form.name', $data['name'])
->set('form.phone_number', $data['phone_number'])
->set('form.address', $data['address'])
@ -82,12 +114,21 @@
->set('form.images', $data['images'])
->call('save')
->assertHasNoErrors()
->assertDispatched('refreshDatatable')
->assertRedirect(Index::class);
$outletModel->refresh();
$this->assertDatabaseHas('outlets', [
'id' => $outlet->id,
'id' => $outletModel->id,
'name' => $data['name'],
'phone_number' => $data['phone_number'],
]);
$this->assertCount(1, $outletModel->getMedia('featured_image'));
$this->assertCount(5, $outletModel->getMedia('images'));
expect($outletModel->getFirstMedia('featured_image')->getPath())->toBeFile();
foreach ($outletModel->getMedia('images') as $media) {
expect($media->getPath())->toBeFile();
}
});

View File

@ -5,45 +5,72 @@
use App\Models\Facility;
use App\Models\OpeningHour;
use App\Models\Outlet;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
it('renders successfully', function () {
Livewire::test(Index::class)
beforeEach(function () {
$this->user = User::factory()->create();
});
function createOutlet(array $attributes = [])
{
return Outlet::factory()
->has(OpeningHour::factory()->count(7))
->has(Facility::factory()->count(5))
->state(fn () => array_merge([
'status' => fake()->randomElement(OutletStatus::cases()),
], $attributes))
->create();
}
function mountIndexComponent(User $user)
{
return Livewire::actingAs($user)->test(Index::class);
}
it('renders page successfully', function () {
mountIndexComponent($this->user)
->assertViewIs('livewire.studio.master.outlet.index')
->assertViewHas('pageTitle', 'Outlet');
});
it('mounts outlets correctly', function () {
$outlet = createOutlet();
mountIndexComponent($this->user)
->assertSet('outlets.0.name', $outlet->name)
->assertSet('outlets.0.phone_number', $outlet->phone_number);
});
it('opens image modal when openImage is called', function () {
$component = mountIndexComponent($this->user);
$url = 'https://example.com/test.jpg';
$component->call('openImage', $url)->assertSet('imageUrl', $url);
});
it('displays all outlets', function () {
Outlet::factory()
->count(10)
->has(OpeningHour::factory()->count(7))
->has(Facility::factory()->count(5))
->state(fn () => [
'status' => fake()->randomElement(OutletStatus::cases()),
])
->create();
for ($i = 0; $i < 5; $i++) {
createOutlet();
}
$outlet = Outlet::first();
$outletCount = Outlet::count();
$firstOutlet = Outlet::first();
Livewire::test(Index::class)->assertSee($outlet->name);
mountIndexComponent($this->user)
->assertSee($firstOutlet->name)
->assertViewHas('outlets', fn ($outlets) => count($outlets) === $outletCount);
});
it('can delete an outlet', function () {
$outlet = Outlet::factory()
->has(OpeningHour::factory()->count(7))
->has(Facility::factory()->count(5))
->state(fn () => [
'status' => fake()->randomElement(OutletStatus::cases()),
])
->create();
it('deletes an outlet successfully', function () {
$outlet = createOutlet();
Livewire::test(Index::class)
mountIndexComponent($this->user)
->call('delete', $outlet)
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
->assertHasNoErrors();
expect(Outlet::count())->toBe(0);
$this->assertSoftDeleted('outlets', ['id' => $outlet->id]);
});