feat: introduce feature tests for Broadcast, FAQ, and Testimonial management, alongside Testimonial model factory.

This commit is contained in:
Yoga Pangestu 2026-01-02 23:18:41 +07:00
parent 332a2c1f98
commit 9b0a1f5ae7
7 changed files with 682 additions and 4 deletions

View File

@ -5,11 +5,14 @@
use App\Enums\IsShow;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Testimonial extends Model
{
use HasFactory;
protected $guarded = ['id'];
protected function casts(): array

View File

@ -0,0 +1,37 @@
<?php
namespace Database\Factories;
use App\Enums\IsShow;
use App\Models\Testimonial;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class TestimonialFactory extends Factory
{
protected $model = Testimonial::class;
public function definition(): array
{
return [
'user_id' => User::factory(),
'content' => $this->faker->paragraph(),
'rating' => $this->faker->numberBetween(1, 5),
'is_show' => IsShow::SHOW,
];
}
public function hidden(): static
{
return $this->state(fn (array $attributes) => [
'is_show' => IsShow::HIDDEN,
]);
}
public function show(): static
{
return $this->state(fn (array $attributes) => [
'is_show' => IsShow::SHOW,
]);
}
}

View File

@ -0,0 +1,200 @@
<?php
use App\Livewire\Studio\Information\Broadcast;
use App\Models\Broadcast as BroadcastModel;
use Illuminate\Support\Facades\Queue;
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' => 'view broadcast']);
Permission::firstOrCreate(['name' => 'create broadcast']);
Permission::firstOrCreate(['name' => 'delete broadcast']);
$role->givePermissionTo(['view broadcast', 'create broadcast', 'delete broadcast']);
$this->user->assignRole($role);
// Create additional roles for testing
$this->customerRole = Role::firstOrCreate(['name' => 'Customer']);
$this->employeeRole = Role::firstOrCreate(['name' => 'Employee']);
$this->developerRole = Role::firstOrCreate(['name' => 'Developer']);
});
it('renders the broadcast page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.information.broadcast.index'))
->assertOk()
->assertSeeLivewire(Broadcast::class);
});
it('can open create modal', function () {
Livewire::actingAs($this->user)
->test(Broadcast::class)
->dispatch('modal:open', method: 'create', modalTitle: 'Tambah Broadcast')
->assertSet('method', 'create')
->assertSet('modalTitle', 'Tambah Broadcast');
});
it('validates required fields on create', function () {
Livewire::actingAs($this->user)
->test(Broadcast::class)
->call('create')
->assertHasErrors([
'form.title' => 'required',
'form.body' => 'required',
'form.role_ids' => 'required',
]);
});
it('validates title max length', function () {
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', str_repeat('a', 31))
->set('form.body', 'Test Body')
->set('form.role_ids', [$this->customerRole->id])
->call('create')
->assertHasErrors(['form.title' => 'max']);
});
it('validates body max length', function () {
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Test Title')
->set('form.body', str_repeat('a', 51))
->set('form.role_ids', [$this->customerRole->id])
->call('create')
->assertHasErrors(['form.body' => 'max']);
});
it('validates link must be valid url', function () {
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Test Title')
->set('form.body', 'Test Body')
->set('form.link', 'not-a-valid-url')
->set('form.role_ids', [$this->customerRole->id])
->call('create')
->assertHasErrors(['form.link' => 'url']);
});
it('validates role_ids must be array with at least one item', function () {
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Test Title')
->set('form.body', 'Test Body')
->set('form.role_ids', [])
->call('create')
->assertHasErrors(['form.role_ids' => 'required']);
});
it('can store a new broadcast', function () {
Queue::fake();
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Broadcast Baru')
->set('form.body', 'Ini adalah broadcast baru')
->set('form.link', 'https://example.com')
->set('form.role_ids', [$this->customerRole->id])
->call('create')
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
$this->assertDatabaseHas('broadcasts', [
'user_id' => $this->user->id,
'title' => 'Broadcast Baru',
'body' => 'Ini adalah broadcast baru',
'link' => 'https://example.com',
]);
$broadcast = BroadcastModel::where('title', 'Broadcast Baru')->first();
$this->assertDatabaseHas('broadcast_audiences', [
'broadcast_id' => $broadcast->id,
'audience_id' => $this->customerRole->id,
]);
});
it('can store broadcast without link', function () {
Queue::fake();
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Broadcast Tanpa Link')
->set('form.body', 'Broadcast ini tidak memiliki link')
->set('form.role_ids', [$this->employeeRole->id])
->call('create')
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
$this->assertDatabaseHas('broadcasts', [
'title' => 'Broadcast Tanpa Link',
'body' => 'Broadcast ini tidak memiliki link',
'link' => null,
]);
});
it('can store broadcast with multiple audiences', function () {
Queue::fake();
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Multi Audience')
->set('form.body', 'Broadcast untuk banyak role')
->set('form.role_ids', [$this->customerRole->id, $this->employeeRole->id])
->call('create')
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
$broadcast = BroadcastModel::where('title', 'Multi Audience')->first();
expect($broadcast->audiences)->toHaveCount(2);
});
it('can delete a broadcast', function () {
$broadcast = BroadcastModel::factory()->create([
'user_id' => $this->user->id,
]);
Livewire::actingAs($this->user)
->test(Broadcast::class)
->call('delete', $broadcast)
->assertDispatched('refreshDatatable');
$this->assertDatabaseMissing('broadcasts', ['id' => $broadcast->id]);
});
it('cannot access broadcast page without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
$this->actingAs($this->user)
->get(route('studio.information.broadcast.index'))
->assertForbidden();
});
it('cannot create broadcast without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Broadcast::class)
->set('form.title', 'Test')
->set('form.body', 'Test')
->set('form.role_ids', [$this->customerRole->id])
->call('create')
->assertForbidden();
});
it('cannot delete broadcast without permission', function () {
$broadcast = BroadcastModel::factory()->create();
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Broadcast::class)
->call('delete', $broadcast)
->assertForbidden();
});

View File

@ -0,0 +1,228 @@
<?php
use App\Livewire\Studio\Information\Faq;
use App\Models\Faq as FaqModel;
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' => 'view faq']);
Permission::firstOrCreate(['name' => 'create faq']);
Permission::firstOrCreate(['name' => 'update faq']);
Permission::firstOrCreate(['name' => 'delete faq']);
$role->givePermissionTo(['view faq', 'create faq', 'update faq', 'delete faq']);
$this->user->assignRole($role);
});
it('renders the faq page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.information.faq.index'))
->assertOk()
->assertSeeLivewire(Faq::class);
});
it('can open create modal', function () {
Livewire::actingAs($this->user)
->test(Faq::class)
->dispatch('modal:open', method: 'create', modalTitle: 'Tambah FAQ')
->assertSet('method', 'create')
->assertSet('modalTitle', 'Tambah FAQ');
});
it('validates required fields on create', function () {
Livewire::actingAs($this->user)
->test(Faq::class)
->call('create')
->assertHasErrors([
'form.question' => 'required',
'form.answer' => 'required',
]);
});
it('validates question max length', function () {
Livewire::actingAs($this->user)
->test(Faq::class)
->set('form.question', str_repeat('a', 101))
->set('form.answer', 'Jawaban')
->call('create')
->assertHasErrors(['form.question' => 'max']);
});
it('can store a new faq', function () {
Livewire::actingAs($this->user)
->test(Faq::class)
->set('form.question', 'Apa itu FAQ?')
->set('form.answer', 'FAQ adalah Frequently Asked Questions')
->call('create')
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
$this->assertDatabaseHas('faqs', [
'question' => 'Apa itu FAQ?',
'answer' => 'FAQ adalah Frequently Asked Questions',
'sort_order' => 1,
]);
});
it('increments sort order when creating new faq', function () {
// Create first FAQ
FaqModel::factory()->create(['sort_order' => 1]);
FaqModel::factory()->create(['sort_order' => 2]);
Livewire::actingAs($this->user)
->test(Faq::class)
->set('form.question', 'Pertanyaan Baru?')
->set('form.answer', 'Jawaban Baru')
->call('create')
->assertHasNoErrors();
// New FAQ should have sort_order 1, and existing ones should be incremented
$newFaq = FaqModel::where('question', 'Pertanyaan Baru?')->first();
expect($newFaq->sort_order)->toBe(1);
// Check that existing FAQs were incremented
expect(FaqModel::where('sort_order', '>', 1)->count())->toBe(2);
});
it('can open edit modal and load data', function () {
$faq = FaqModel::factory()->create([
'question' => 'Pertanyaan Edit?',
'answer' => 'Jawaban Edit',
]);
Livewire::actingAs($this->user)
->test(Faq::class)
->call('openModal', 'update', 'Ubah FAQ', $faq->hash_id)
->assertSet('method', 'update')
->assertSet('modalTitle', 'Ubah FAQ');
});
it('can update a faq', function () {
$faq = FaqModel::factory()->create([
'question' => 'Pertanyaan Lama?',
'answer' => 'Jawaban Lama',
]);
Livewire::actingAs($this->user)
->test(Faq::class)
->set('form.faq', $faq)
->set('form.question', 'Pertanyaan Diperbarui?')
->set('form.answer', 'Jawaban Diperbarui')
->call('update')
->assertHasNoErrors()
->assertDispatched('refreshDatatable');
$this->assertDatabaseHas('faqs', [
'id' => $faq->id,
'question' => 'Pertanyaan Diperbarui?',
'answer' => 'Jawaban Diperbarui',
]);
});
it('can delete a faq', function () {
$faq = FaqModel::factory()->create(['sort_order' => 2]);
Livewire::actingAs($this->user)
->test(Faq::class)
->call('delete', $faq)
->assertDispatched('refreshDatatable');
$this->assertSoftDeleted('faqs', ['id' => $faq->id]);
});
it('can move faq sort order up', function () {
$faq1 = FaqModel::factory()->create(['sort_order' => 1]);
$faq2 = FaqModel::factory()->create(['sort_order' => 2]);
$faq3 = FaqModel::factory()->create(['sort_order' => 3]);
Livewire::actingAs($this->user)
->test(Faq::class)
->call('sortOrder', $faq2, 'up')
->assertDispatched('refreshDatatable');
$faq1->refresh();
$faq2->refresh();
expect($faq2->sort_order)->toBe(1);
expect($faq1->sort_order)->toBe(2);
});
it('can move faq sort order down', function () {
$faq1 = FaqModel::factory()->create(['sort_order' => 1]);
$faq2 = FaqModel::factory()->create(['sort_order' => 2]);
$faq3 = FaqModel::factory()->create(['sort_order' => 3]);
Livewire::actingAs($this->user)
->test(Faq::class)
->call('sortOrder', $faq2, 'down')
->assertDispatched('refreshDatatable');
$faq2->refresh();
$faq3->refresh();
expect($faq2->sort_order)->toBe(3);
expect($faq3->sort_order)->toBe(2);
});
it('cannot access faq page without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
$this->actingAs($this->user)
->get(route('studio.information.faq.index'))
->assertForbidden();
});
it('cannot create faq without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Faq::class)
->set('form.question', 'Test?')
->set('form.answer', 'Test')
->call('create')
->assertForbidden();
});
it('cannot update faq without permission', function () {
$faq = FaqModel::factory()->create();
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Faq::class)
->call('openModal', 'update', 'Ubah FAQ', $faq->hash_id)
->set('form.question', 'Updated?')
->call('update')
->assertForbidden();
});
it('cannot delete faq without permission', function () {
$faq = FaqModel::factory()->create();
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Faq::class)
->call('delete', $faq)
->assertForbidden();
});
it('cannot sort faq without permission', function () {
$faq = FaqModel::factory()->create(['sort_order' => 2]);
$this->user->roles()->detach();
$this->user->permissions()->detach();
Livewire::actingAs($this->user)
->test(Faq::class)
->call('sortOrder', $faq, 'up')
->assertForbidden();
});

View File

@ -0,0 +1,212 @@
<?php
use App\Enums\IsShow;
use App\Livewire\Datatable\Information\TestimonialsTable;
use App\Livewire\Studio\Information\Testimonial;
use App\Models\Customer;
use App\Models\Testimonial as TestimonialModel;
use App\Models\User;
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' => 'view testimonial']);
$role->givePermissionTo(['view testimonial']);
$this->user->assignRole($role);
});
it('renders the testimonial page correctly', function () {
$this->actingAs($this->user)
->get(route('studio.information.testimonial.index'))
->assertOk()
->assertSeeLivewire(Testimonial::class);
});
it('displays testimonials in datatable', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id]);
$testimonial = TestimonialModel::factory()->create([
'user_id' => $user->id,
'content' => 'Produk sangat bagus dan berkualitas',
'rating' => 5,
'is_show' => IsShow::SHOW,
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->assertSee($customer->name)
->assertSee('Produk sangat bagus dan berkualitas')
->assertSee('5');
});
it('can toggle testimonial visibility to hidden', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id]);
$testimonial = TestimonialModel::factory()->create([
'user_id' => $user->id,
'is_show' => IsShow::SHOW,
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->call('toggleShow', $testimonial)
->assertOk();
$testimonial->refresh();
expect($testimonial->is_show)->toBe(IsShow::HIDDEN);
$this->assertDatabaseHas('testimonials', [
'id' => $testimonial->id,
'is_show' => IsShow::HIDDEN->value,
]);
});
it('can toggle testimonial visibility to show', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id]);
$testimonial = TestimonialModel::factory()->hidden()->create([
'user_id' => $user->id,
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->call('toggleShow', $testimonial)
->assertOk();
$testimonial->refresh();
expect($testimonial->is_show)->toBe(IsShow::SHOW);
$this->assertDatabaseHas('testimonials', [
'id' => $testimonial->id,
'is_show' => IsShow::SHOW->value,
]);
});
it('filters testimonials by show scope', function () {
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$customer1 = Customer::factory()->create(['user_id' => $user1->id]);
$customer2 = Customer::factory()->create(['user_id' => $user2->id]);
$shownTestimonial = TestimonialModel::factory()->show()->create([
'user_id' => $user1->id,
'content' => 'Testimonial yang ditampilkan',
]);
$hiddenTestimonial = TestimonialModel::factory()->hidden()->create([
'user_id' => $user2->id,
'content' => 'Testimonial yang disembunyikan',
]);
$shownTestimonials = TestimonialModel::query()->show()->get();
expect($shownTestimonials->pluck('id'))->toContain($shownTestimonial->id);
expect($shownTestimonials->pluck('id'))->not->toContain($hiddenTestimonial->id);
});
it('displays testimonial with correct rating', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id]);
$testimonial = TestimonialModel::factory()->create([
'user_id' => $user->id,
'rating' => 4,
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->assertSee('4');
expect($testimonial->rating)->toBe(4);
});
it('truncates long testimonial content in datatable', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id]);
$longContent = str_repeat('Ini adalah testimonial yang sangat panjang. ', 10);
$testimonial = TestimonialModel::factory()->create([
'user_id' => $user->id,
'content' => $longContent,
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->assertSee('...');
});
it('shows testimonial with user relationship', function () {
$user = User::factory()->create();
$customer = Customer::factory()->create(['user_id' => $user->id, 'name' => 'John Doe']);
$testimonial = TestimonialModel::factory()->create([
'user_id' => $user->id,
]);
$testimonial->load('user.customer');
expect($testimonial->user)->not->toBeNull();
expect($testimonial->user->customer->name)->toBe('John Doe');
});
it('can search testimonials by customer name', function () {
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$customer1 = Customer::factory()->create(['user_id' => $user1->id, 'name' => 'Alice Johnson']);
$customer2 = Customer::factory()->create(['user_id' => $user2->id, 'name' => 'Bob Smith']);
TestimonialModel::factory()->create([
'user_id' => $user1->id,
'content' => 'Great product!',
]);
TestimonialModel::factory()->create([
'user_id' => $user2->id,
'content' => 'Excellent service!',
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->set('search', 'Alice')
->assertSee('Alice Johnson')
->assertDontSee('Bob Smith');
});
it('can search testimonials by content', function () {
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$customer1 = Customer::factory()->create(['user_id' => $user1->id]);
$customer2 = Customer::factory()->create(['user_id' => $user2->id]);
TestimonialModel::factory()->create([
'user_id' => $user1->id,
'content' => 'Produk sangat memuaskan',
]);
TestimonialModel::factory()->create([
'user_id' => $user2->id,
'content' => 'Pelayanan ramah',
]);
Livewire::actingAs($this->user)
->test(TestimonialsTable::class)
->set('search', 'memuaskan')
->assertSee('Produk sangat memuaskan')
->assertDontSee('Pelayanan ramah');
});
it('cannot access testimonial page without permission', function () {
$this->user->roles()->detach();
$this->user->permissions()->detach();
$this->actingAs($this->user)
->get(route('studio.information.testimonial.index'))
->assertForbidden();
});

View File

@ -1,9 +1,7 @@
<?php
use App\Enums\CategoryType;
use App\Livewire\Studio\Manage\Article\Index;
use App\Models\Article;
use App\Models\Category;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

View File

@ -47,8 +47,8 @@
Livewire::actingAs($this->user)
->test(Manage::class, ['stockOpname' => $stockOpname])
->set('form.outlet_stock.' . $item->id, 8) // Physical count is 8
->assertSet('form.outlet_stock.' . $item->id, 8);
->set('form.outlet_stock.'.$item->id, 8) // Physical count is 8
->assertSet('form.outlet_stock.'.$item->id, 8);
$this->assertDatabaseHas('stock_opname_items', [
'id' => $item->id,