feat(breadcat): membuat fitur hapus
- implementasi testing - use HashableId di model - kasih return type - menyesuaikan factory - menyesuaikan modalname - menambahkan permission untuk delet
This commit is contained in:
parent
fb01f6ce6c
commit
c75d750de0
@ -37,8 +37,14 @@ public function columns(): array
|
|||||||
</div>', ['value' => $value]))
|
</div>', ['value' => $value]))
|
||||||
->flexRow(['class' => 'gap-2 flex-wrap']),
|
->flexRow(['class' => 'gap-2 flex-wrap']),
|
||||||
|
|
||||||
Column::make('Tanggal')
|
Column::make('Aksi')
|
||||||
->label(fn ($row, $column) => formatDateTime($row->created_at)),
|
->label(function ($row) {
|
||||||
|
return view('components.actions.table.delete', [
|
||||||
|
'id' => $row->hash,
|
||||||
|
])->render();
|
||||||
|
})
|
||||||
|
->html()
|
||||||
|
->hideIf(auth()->user()->cannot('delete broadcast')),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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->title = $broadcast->title;
|
||||||
$this->body = $broadcast->body;
|
$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();
|
$this->validate();
|
||||||
|
|
||||||
@ -61,16 +63,13 @@ public function store()
|
|||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'status' => true,
|
'title' => $broadcast->title,
|
||||||
'data' => [
|
'body' => $broadcast->body,
|
||||||
'title' => $broadcast->title,
|
'audience_ids' => $this->role_ids,
|
||||||
'body' => $broadcast->body,
|
|
||||||
'audience_ids' => $this->role_ids,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update()
|
public function update(): void
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
use App\Traits\WithToast;
|
use App\Traits\WithToast;
|
||||||
use App\Traits\WithUpdatedData;
|
use App\Traits\WithUpdatedData;
|
||||||
use Flux\Flux;
|
use Flux\Flux;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
use Livewire\Attributes\On;
|
use Livewire\Attributes\On;
|
||||||
use Livewire\Attributes\Title;
|
use Livewire\Attributes\Title;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@ -32,13 +33,49 @@ class Broadcast extends Component
|
|||||||
|
|
||||||
public array $roles = [];
|
public array $roles = [];
|
||||||
|
|
||||||
public function mount()
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->roles = Role::where('name', '!=', 'Developer')->orderBy('id')->pluck('name', 'id')->toArray();
|
$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')]
|
#[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->resetValidation();
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
@ -51,53 +88,7 @@ public function openModal(string $method, string $modalTitle, ?string $id = null
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
public function render(): View
|
||||||
{
|
|
||||||
$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()
|
|
||||||
{
|
{
|
||||||
return view('livewire.studio.information.broadcasts', [
|
return view('livewire.studio.information.broadcasts', [
|
||||||
'pageTitle' => 'Broadcast',
|
'pageTitle' => 'Broadcast',
|
||||||
|
|||||||
@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Veelasky\LaravelHashId\Eloquent\HashableId;
|
||||||
|
|
||||||
class Broadcast extends Model
|
class Broadcast extends Model
|
||||||
{
|
{
|
||||||
|
use HasFactory, HashableId;
|
||||||
|
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
|
|||||||
@ -14,8 +14,8 @@ public function definition(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'user_id' => User::factory(),
|
'user_id' => User::factory(),
|
||||||
'title' => $this->faker->sentence(4),
|
'title' => 'Sample Broadcast Title',
|
||||||
'body' => $this->faker->paragraph(),
|
'body' => 'Sample broadcast message content.',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -134,6 +134,7 @@ public function run(): void
|
|||||||
|
|
||||||
'view broadcast',
|
'view broadcast',
|
||||||
'create broadcast',
|
'create broadcast',
|
||||||
|
'delete broadcast',
|
||||||
|
|
||||||
'view perfume feature',
|
'view perfume feature',
|
||||||
'create perfume feature',
|
'create perfume feature',
|
||||||
|
|||||||
@ -19,13 +19,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include('components.modals.confirmation', [
|
@include('components.modals.confirmation', [
|
||||||
'modalName' => 'confirmation-modal',
|
'modalName' => 'delete-confirmation',
|
||||||
'modalTitle' => 'Apakah Anda yakin?',
|
'modalTitle' => 'Apakah Anda yakin?',
|
||||||
'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.',
|
'modalMessage' => 'Data yang berelasi dengan data ini juga akan ikut terhapus.',
|
||||||
'buttonVariant' => 'primary',
|
'buttonVariant' => 'primary',
|
||||||
'buttonColor' => 'danger',
|
'buttonColor' => 'danger',
|
||||||
'buttonText' => 'Ya, Hapus',
|
'buttonText' => 'Ya, Hapus',
|
||||||
])
|
])
|
||||||
|
|
||||||
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
|
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
|
||||||
<div class="p-4 space-y-6">
|
<div class="p-4 space-y-6">
|
||||||
<flux:heading size="lg">{{ $modalTitle }}</flux:heading>
|
<flux:heading size="lg">{{ $modalTitle }}</flux:heading>
|
||||||
|
|||||||
194
tests/Feature/Livewire/Studio/Information/BroadcastTest.php
Normal file
194
tests/Feature/Livewire/Studio/Information/BroadcastTest.php
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\StorePushNotification;
|
||||||
|
use App\Livewire\Studio\Information\Broadcast;
|
||||||
|
use App\Models\Broadcast as BroadcastModel;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Create permissions
|
||||||
|
Permission::create(['name' => '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);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user