diff --git a/app/Models/Testimonial.php b/app/Models/Testimonial.php index 026c30c..f711a1c 100644 --- a/app/Models/Testimonial.php +++ b/app/Models/Testimonial.php @@ -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 diff --git a/database/factories/TestimonialFactory.php b/database/factories/TestimonialFactory.php new file mode 100644 index 0000000..e0cc8f1 --- /dev/null +++ b/database/factories/TestimonialFactory.php @@ -0,0 +1,37 @@ + 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, + ]); + } +} diff --git a/tests/Feature/Studio/Information/BroadcastTest.php b/tests/Feature/Studio/Information/BroadcastTest.php new file mode 100644 index 0000000..7186f46 --- /dev/null +++ b/tests/Feature/Studio/Information/BroadcastTest.php @@ -0,0 +1,200 @@ +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(); +}); diff --git a/tests/Feature/Studio/Information/FaqTest.php b/tests/Feature/Studio/Information/FaqTest.php new file mode 100644 index 0000000..d58fca2 --- /dev/null +++ b/tests/Feature/Studio/Information/FaqTest.php @@ -0,0 +1,228 @@ +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(); +}); diff --git a/tests/Feature/Studio/Information/TestimonialTest.php b/tests/Feature/Studio/Information/TestimonialTest.php new file mode 100644 index 0000000..f331bff --- /dev/null +++ b/tests/Feature/Studio/Information/TestimonialTest.php @@ -0,0 +1,212 @@ +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(); +}); diff --git a/tests/Feature/Studio/Manage/Article/IndexTest.php b/tests/Feature/Studio/Manage/Article/IndexTest.php index 4ec63c7..45990f9 100644 --- a/tests/Feature/Studio/Manage/Article/IndexTest.php +++ b/tests/Feature/Studio/Manage/Article/IndexTest.php @@ -1,9 +1,7 @@ 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,