From c75d750de0c40ce12bb9f99fa9d858e5a2c5e50d Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 10 Dec 2025 13:25:47 +0700 Subject: [PATCH] feat(breadcat): membuat fitur hapus - implementasi testing - use HashableId di model - kasih return type - menyesuaikan factory - menyesuaikan modalname - menambahkan permission untuk delet --- .../Studio/Information/BroadcastsTable.php | 10 +- .../Studio/Information/BroadcastForm.php | 19 +- app/Livewire/Studio/Information/Broadcast.php | 89 ++++---- app/Models/Broadcast.php | 4 + database/factories/BroadcastFactory.php | 4 +- database/seeders/RolePermissionSeeder.php | 1 + .../studio/information/broadcasts.blade.php | 3 +- .../Studio/Information/BroadcastTest.php | 194 ++++++++++++++++++ 8 files changed, 260 insertions(+), 64 deletions(-) create mode 100644 tests/Feature/Livewire/Studio/Information/BroadcastTest.php diff --git a/app/Livewire/Datatable/Studio/Information/BroadcastsTable.php b/app/Livewire/Datatable/Studio/Information/BroadcastsTable.php index 08bc8e1..08626ee 100644 --- a/app/Livewire/Datatable/Studio/Information/BroadcastsTable.php +++ b/app/Livewire/Datatable/Studio/Information/BroadcastsTable.php @@ -37,8 +37,14 @@ public function columns(): array ', ['value' => $value])) ->flexRow(['class' => 'gap-2 flex-wrap']), - Column::make('Tanggal') - ->label(fn ($row, $column) => formatDateTime($row->created_at)), + Column::make('Aksi') + ->label(function ($row) { + return view('components.actions.table.delete', [ + 'id' => $row->hash, + ])->render(); + }) + ->html() + ->hideIf(auth()->user()->cannot('delete broadcast')), ]; } diff --git a/app/Livewire/Forms/Studio/Information/BroadcastForm.php b/app/Livewire/Forms/Studio/Information/BroadcastForm.php index 3236c59..c26b43c 100644 --- a/app/Livewire/Forms/Studio/Information/BroadcastForm.php +++ b/app/Livewire/Forms/Studio/Information/BroadcastForm.php @@ -39,14 +39,16 @@ public function validationAttributes(): array ]; } - public function setBroadcast(Broadcast $broadcast) + public function setBroadcast(Broadcast $broadcast): void { + $this->broadcast = $broadcast; + $this->title = $broadcast->title; $this->body = $broadcast->body; - $this->role_ids = $broadcast->roles->pluck('id')->toArray(); + $this->role_ids = $broadcast->audiences->pluck('id')->toArray(); } - public function store() + public function store(): array { $this->validate(); @@ -61,16 +63,13 @@ public function store() }); return [ - 'status' => true, - 'data' => [ - 'title' => $broadcast->title, - 'body' => $broadcast->body, - 'audience_ids' => $this->role_ids, - ], + 'title' => $broadcast->title, + 'body' => $broadcast->body, + 'audience_ids' => $this->role_ids, ]; } - public function update() + public function update(): void { $this->validate(); diff --git a/app/Livewire/Studio/Information/Broadcast.php b/app/Livewire/Studio/Information/Broadcast.php index 4d4056f..dffd59e 100644 --- a/app/Livewire/Studio/Information/Broadcast.php +++ b/app/Livewire/Studio/Information/Broadcast.php @@ -14,6 +14,7 @@ use App\Traits\WithToast; use App\Traits\WithUpdatedData; use Flux\Flux; +use Illuminate\Contracts\View\View; use Livewire\Attributes\On; use Livewire\Attributes\Title; use Livewire\Component; @@ -32,13 +33,49 @@ class Broadcast extends Component public array $roles = []; - public function mount() + public function mount(): void { $this->roles = Role::where('name', '!=', 'Developer')->orderBy('id')->pluck('name', 'id')->toArray(); } + public function create(): void + { + $this->canOrAbort('create broadcast'); + + $result = $this->form->store(); + + $userIds = User::role(array_map('intval', $result['audience_ids']))->pluck('id')->toArray(); + + StorePushNotification::dispatch( + PushNotification::whereIn('user_id', $userIds)->get(), + [ + 'title' => $result['title'], + 'body' => $result['body'], + ], + ); + + $this->dispatch('refreshDatatable'); + + $this->toast('Broadcast berhasil ditambahkan.'); + + Flux::modals()->close(); + } + + public function delete(BroadcastModel $broadcast): void + { + $this->canOrAbort('delete broadcast'); + + $broadcast->delete(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Broadcast berhasil dihapus.'); + + Flux::modals()->close(); + } + #[On('modal:open')] - public function openModal(string $method, string $modalTitle, ?string $id = null) + public function openModal(string $method, string $modalTitle, ?string $id = null): void { $this->resetValidation(); $this->resetErrorBag(); @@ -51,53 +88,7 @@ public function openModal(string $method, string $modalTitle, ?string $id = null } } - public function create() - { - $this->canOrAbort('create broadcast'); - - $result = $this->form->store(); - - $userIds = User::role(array_map('intval', $result['data']['audience_ids']))->pluck('id')->toArray(); - StorePushNotification::dispatch( - PushNotification::whereIn('user_id', $userIds)->get(), - [ - 'title' => $result['data']['title'], - 'body' => $result['data']['body'], - ], - ); - - $this->dispatch('refreshDatatable'); - - $this->toast('Broadcast berhasil ditambahkan.'); - - Flux::modals()->close(); - } - - public function update() - { - $this->canOrAbort('update broadcast'); - - $this->form->update(); - - $this->dispatch('refreshDatatable'); - - $this->toast('Broadcast berhasil diperbarui.'); - - Flux::modals()->close(); - } - - public function delete(BroadcastModel $broadcast) - { - $broadcast->delete(); - - $this->dispatch('refreshDatatable'); - - $this->toast('Broadcast berhasil dihapus.'); - - Flux::modals()->close(); - } - - public function render() + public function render(): View { return view('livewire.studio.information.broadcasts', [ 'pageTitle' => 'Broadcast', diff --git a/app/Models/Broadcast.php b/app/Models/Broadcast.php index 5d3124a..4d119ee 100644 --- a/app/Models/Broadcast.php +++ b/app/Models/Broadcast.php @@ -2,13 +2,17 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Spatie\Permission\Models\Role; +use Veelasky\LaravelHashId\Eloquent\HashableId; class Broadcast extends Model { + use HasFactory, HashableId; + protected $guarded = ['id']; public function user(): BelongsTo diff --git a/database/factories/BroadcastFactory.php b/database/factories/BroadcastFactory.php index b31dc09..782fac2 100644 --- a/database/factories/BroadcastFactory.php +++ b/database/factories/BroadcastFactory.php @@ -14,8 +14,8 @@ public function definition(): array { return [ 'user_id' => User::factory(), - 'title' => $this->faker->sentence(4), - 'body' => $this->faker->paragraph(), + 'title' => 'Sample Broadcast Title', + 'body' => 'Sample broadcast message content.', ]; } } diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index 229b6b0..846168a 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -134,6 +134,7 @@ public function run(): void 'view broadcast', 'create broadcast', + 'delete broadcast', 'view perfume feature', 'create perfume feature', diff --git a/resources/views/livewire/studio/information/broadcasts.blade.php b/resources/views/livewire/studio/information/broadcasts.blade.php index 3f07e0a..a06da44 100644 --- a/resources/views/livewire/studio/information/broadcasts.blade.php +++ b/resources/views/livewire/studio/information/broadcasts.blade.php @@ -19,13 +19,14 @@ @include('components.modals.confirmation', [ - 'modalName' => 'confirmation-modal', + 'modalName' => 'delete-confirmation', 'modalTitle' => 'Apakah Anda yakin?', 'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.', 'buttonVariant' => 'primary', 'buttonColor' => 'danger', 'buttonText' => 'Ya, Hapus', ]) +
{{ $modalTitle }} diff --git a/tests/Feature/Livewire/Studio/Information/BroadcastTest.php b/tests/Feature/Livewire/Studio/Information/BroadcastTest.php new file mode 100644 index 0000000..cc27aa0 --- /dev/null +++ b/tests/Feature/Livewire/Studio/Information/BroadcastTest.php @@ -0,0 +1,194 @@ + 'create broadcast', 'guard_name' => 'web']); + Permission::create(['name' => 'update broadcast', 'guard_name' => 'web']); + Permission::create(['name' => 'delete broadcast', 'guard_name' => 'web']); + + // Create role and assign permissions + $role = Role::create(['name' => 'Admin', 'guard_name' => 'web']); + $role->givePermissionTo(['create broadcast', 'update broadcast', 'delete broadcast']); + + // Create admin user + $this->adminUser = User::factory()->create(); + $this->adminUser->assignRole('Admin'); + + // Create additional roles for testing + Role::create(['name' => 'User', 'guard_name' => 'web']); + Role::create(['name' => 'Manager', 'guard_name' => 'web']); +}); + +it('renders page successfully', function () { + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->assertOk() + ->assertSet('method', 'create') + ->assertSet('modalTitle', '') + ->assertSee('Broadcast') + ->assertSet('roles', function ($roles) { + return count($roles) > 0 && ! isset($roles['Developer']); + }); +}); + +it('has title attribute', function () { + $component = new Broadcast; + $reflection = new \ReflectionClass($component); + $attributes = $reflection->getAttributes(\Livewire\Attributes\Title::class); + + expect($attributes)->not->toBeEmpty(); +}); + +it('excludes developer role on mount', function () { + Role::create(['name' => 'Developer', 'guard_name' => 'web']); + + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->assertSet('roles', function ($roles) { + return ! isset($roles['Developer']); + }); +}); + +it('opens modal for create', function () { + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->call('openModal', 'create', 'Tambah Broadcast') + ->assertSet('method', 'create') + ->assertSet('modalTitle', 'Tambah Broadcast') + ->assertSet('form.title', '') + ->assertSet('form.body', '') + ->assertSet('form.role_ids', []); +}); + +it('opens modal for update', function () { + $broadcast = BroadcastModel::factory()->create([ + 'title' => 'Test Title', + 'body' => 'Test Body', + ]); + + $role = Role::create(['name' => 'Test Role', 'guard_name' => 'web']); + $broadcast->audiences()->attach($role->id); + + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->call('openModal', 'update', 'Ubah Broadcast', $broadcast->hash) + ->assertSet('method', 'update') + ->assertSet('modalTitle', 'Ubah Broadcast'); +}); + +it('creates broadcast successfully', function () { + Queue::fake(); + + $role1 = Role::where('name', 'User')->first(); + $role2 = Role::where('name', 'Manager')->first(); + + $user1 = User::factory()->create(); + $user2 = User::factory()->create(); + $user1->assignRole('User'); + $user2->assignRole('Manager'); + + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->set('form.title', 'Important Announcement') + ->set('form.body', 'This is a test broadcast message.') + ->set('form.role_ids', [$role1->id, $role2->id]) + ->call('create') + ->assertDispatched('refreshDatatable'); + + $this->assertDatabaseHas('broadcasts', [ + 'user_id' => $this->adminUser->id, + 'title' => 'Important Announcement', + 'body' => 'This is a test broadcast message.', + ]); + + $broadcast = BroadcastModel::latest()->first(); + expect($broadcast->audiences)->toHaveCount(2); + + Queue::assertPushed(StorePushNotification::class); +}); + +it('requires permission to create broadcast', function () { + $userWithoutPermission = User::factory()->create(); + + Livewire::actingAs($userWithoutPermission) + ->test(Broadcast::class) + ->set('form.title', 'Test Title') + ->set('form.body', 'Test Body') + ->set('form.role_ids', [1]) + ->call('create') + ->assertForbidden(); +}); + +it('broadcasts cannot be updated', function () { + $broadcast = BroadcastModel::factory()->create(); + + // Broadcast component doesn't have update method, only create and delete + expect(method_exists(Broadcast::class, 'update'))->toBeFalse(); +}); + +it('broadcasts do not support update permission check', function () { + // Broadcast component doesn't have update method, so no update permission test needed + expect(method_exists(Broadcast::class, 'update'))->toBeFalse(); +}); + +it('deletes broadcast successfully', function () { + $broadcast = BroadcastModel::factory()->create(); + + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->call('delete', $broadcast) + ->assertDispatched('refreshDatatable'); + + $this->assertDatabaseMissing('broadcasts', [ + 'id' => $broadcast->id, + ]); +}); + +it('displays validation errors', function () { + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->set('form.title', '') // Empty title + ->set('form.body', '') // Empty body + ->set('form.role_ids', []) // Empty role_ids + ->call('create') + ->assertHasErrors(['form.title', 'form.body', 'form.role_ids']); +}); + +it('opens modal correctly', function () { + $broadcast = BroadcastModel::factory()->create(); + + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->call('openModal', 'create', 'Tambah Broadcast') + ->assertSet('method', 'create') + ->assertSet('modalTitle', 'Tambah Broadcast'); +}); + +it('creates broadcast with push notification job', function () { + Queue::fake(); + + $role = Role::where('name', 'User')->first(); + $user = User::factory()->create(); + $user->assignRole('User'); + + Livewire::actingAs($this->adminUser) + ->test(Broadcast::class) + ->set('form.title', 'Test Title') + ->set('form.body', 'Test Body') + ->set('form.role_ids', [$role->id]) + ->call('create'); + + Queue::assertPushed(StorePushNotification::class); +});