diff --git a/app/Livewire/Studio/Master/Outlet/Edit.php b/app/Livewire/Studio/Master/Outlet/Edit.php index cc5fed0..7936c8e 100644 --- a/app/Livewire/Studio/Master/Outlet/Edit.php +++ b/app/Livewire/Studio/Master/Outlet/Edit.php @@ -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() diff --git a/app/Livewire/Studio/Master/Outlet/Index.php b/app/Livewire/Studio/Master/Outlet/Index.php index 3ffd48c..faaedd0 100644 --- a/app/Livewire/Studio/Master/Outlet/Index.php +++ b/app/Livewire/Studio/Master/Outlet/Index.php @@ -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(); diff --git a/app/Models/Outlet.php b/app/Models/Outlet.php index 153a689..29e40d9 100644 --- a/app/Models/Outlet.php +++ b/app/Models/Outlet.php @@ -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 { diff --git a/database/factories/OutletFactory.php b/database/factories/OutletFactory.php index 3ca6963..31c6136 100644 --- a/database/factories/OutletFactory.php +++ b/database/factories/OutletFactory.php @@ -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(), ]; } diff --git a/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php b/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php index 4f609b2..614211f 100644 --- a/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php +++ b/tests/Feature/Livewire/Studio/Master/Outlet/CreateTest.php @@ -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(); } diff --git a/tests/Feature/Livewire/Studio/Master/Outlet/EditTest.php b/tests/Feature/Livewire/Studio/Master/Outlet/EditTest.php index 3daa44c..4dbddf2 100644 --- a/tests/Feature/Livewire/Studio/Master/Outlet/EditTest.php +++ b/tests/Feature/Livewire/Studio/Master/Outlet/EditTest.php @@ -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(); + } }); diff --git a/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php b/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php index 001ce0b..b62b349 100644 --- a/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php +++ b/tests/Feature/Livewire/Studio/Master/Outlet/IndexTest.php @@ -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]); });