seed(RolePermissionSeeder::class); $this->user = User::factory() ->active() ->has(Employee::factory()) ->create(); $this->ownerRole = Role::where('name', 'Owner')->first(); $this->user->roles()->attach($this->ownerRole->id); }); function mountIndexComponent(User $user) { return Livewire::actingAs($user)->test(Index::class); } /* |-------------------------------------------------------------------------- | Component Rendering & Mount |-------------------------------------------------------------------------- */ it('renders page successfully', function () { mountIndexComponent($this->user) ->assertViewIs('livewire.studio.loyalty.voucher.index') ->assertViewHas('pageTitle', 'Voucher'); }); it('displays all vouchers', function () { Voucher::factory()->count(10)->create(); $voucher = Voucher::first(); mountIndexComponent($this->user) ->assertSee($voucher->name); }); /* |-------------------------------------------------------------------------- | Statistics |-------------------------------------------------------------------------- */ it('displays correct total voucher count', function () { Voucher::factory()->count(5)->create(); $component = mountIndexComponent($this->user); expect($component->stats)->toHaveCount(4); expect($component->stats[0]['title'])->toBe('Total Voucher'); expect($component->stats[0]['value'])->toBe(5); }); it('displays correct active voucher count', function () { // Active vouchers: start_date <= now AND (end_date is null OR end_date >= now) Voucher::factory()->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => now()->addDay()->format('Y-m-d'), ]); Voucher::factory()->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => null, ]); Voucher::factory()->create([ 'start_date' => now()->subWeek()->format('Y-m-d'), 'end_date' => now()->addWeek()->format('Y-m-d'), ]); // Inactive vouchers Voucher::factory()->create([ 'start_date' => now()->subWeek()->format('Y-m-d'), 'end_date' => now()->subDay()->format('Y-m-d'), ]); // Upcoming vouchers Voucher::factory()->create([ 'start_date' => now()->addDay()->format('Y-m-d'), 'end_date' => now()->addWeek()->format('Y-m-d'), ]); $component = mountIndexComponent($this->user); expect($component->stats[1]['title'])->toBe('Voucher Aktif'); expect($component->stats[1]['value'])->toBe(3); }); it('displays correct inactive voucher count', function () { // Inactive vouchers: end_date < now Voucher::factory()->create([ 'start_date' => now()->subWeek()->format('Y-m-d'), 'end_date' => now()->subDay()->format('Y-m-d'), ]); Voucher::factory()->create([ 'start_date' => now()->subMonth()->format('Y-m-d'), 'end_date' => now()->subWeek()->format('Y-m-d'), ]); // Active vouchers Voucher::factory()->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => now()->addDay()->format('Y-m-d'), ]); // Upcoming vouchers Voucher::factory()->create([ 'start_date' => now()->addDay()->format('Y-m-d'), 'end_date' => now()->addWeek()->format('Y-m-d'), ]); $component = mountIndexComponent($this->user); expect($component->stats[2]['title'])->toBe('Voucher Tidak Aktif'); expect($component->stats[2]['value'])->toBe(2); }); it('displays correct upcoming voucher count', function () { // Upcoming vouchers: start_date > now Voucher::factory()->create([ 'start_date' => now()->addDay()->format('Y-m-d'), 'end_date' => now()->addWeek()->format('Y-m-d'), ]); Voucher::factory()->create([ 'start_date' => now()->addWeek()->format('Y-m-d'), 'end_date' => now()->addMonth()->format('Y-m-d'), ]); // Active vouchers Voucher::factory()->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => now()->addDay()->format('Y-m-d'), ]); // Inactive vouchers Voucher::factory()->create([ 'start_date' => now()->subWeek()->format('Y-m-d'), 'end_date' => now()->subDay()->format('Y-m-d'), ]); $component = mountIndexComponent($this->user); expect($component->stats[3]['title'])->toBe('Voucher Akan Datang'); expect($component->stats[3]['value'])->toBe(2); }); it('calculates stats correctly with mixed voucher statuses', function () { // Create vouchers with different statuses Voucher::factory()->count(3)->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => now()->addDay()->format('Y-m-d'), ]); Voucher::factory()->count(2)->create([ 'start_date' => now()->subWeek()->format('Y-m-d'), 'end_date' => now()->subDay()->format('Y-m-d'), ]); Voucher::factory()->count(4)->create([ 'start_date' => now()->addDay()->format('Y-m-d'), 'end_date' => now()->addWeek()->format('Y-m-d'), ]); $component = mountIndexComponent($this->user); expect($component->stats[0]['value'])->toBe(9); // Total expect($component->stats[1]['value'])->toBe(3); // Active expect($component->stats[2]['value'])->toBe(2); // Inactive expect($component->stats[3]['value'])->toBe(4); // Upcoming }); it('handles vouchers with null end_date in stats', function () { Voucher::factory()->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => null, // Never expires ]); Voucher::factory()->create([ 'start_date' => now()->addDay()->format('Y-m-d'), 'end_date' => null, // Upcoming, never expires ]); $component = mountIndexComponent($this->user); expect($component->stats[1]['value'])->toBe(1); // Active (started, no end date) expect($component->stats[3]['value'])->toBe(1); // Upcoming (not started yet) }); /* |-------------------------------------------------------------------------- | Delete Voucher |-------------------------------------------------------------------------- */ it('can delete a voucher', function () { $voucher = Voucher::factory()->create(); mountIndexComponent($this->user) ->call('delete', $voucher) ->assertHasNoErrors() ->assertDispatched('refreshDatatable'); expect(Voucher::count())->toBe(0); $this->assertSoftDeleted('vouchers', ['id' => $voucher->id]); }); it('can delete multiple vouchers', function () { $vouchers = Voucher::factory()->count(5)->create(); $component = mountIndexComponent($this->user); foreach ($vouchers as $voucher) { $component->call('delete', $voucher); } expect(Voucher::count())->toBe(0); }); it('soft deletes voucher when deleting', function () { $voucher = Voucher::factory()->create(); mountIndexComponent($this->user) ->call('delete', $voucher); expect(Voucher::withTrashed()->count())->toBe(1); expect(Voucher::count())->toBe(0); expect($voucher->fresh()->trashed())->toBeTrue(); }); /* |-------------------------------------------------------------------------- | Permissions |-------------------------------------------------------------------------- */ it('displays create button when user has create voucher permission', function () { mountIndexComponent($this->user) ->assertSee('Tambah'); }); it('hides create button when user lacks create voucher permission', function () { $this->ownerRole->revokePermissionTo('create voucher'); Gate::define('create voucher', fn () => false); mountIndexComponent($this->user) ->assertDontSee('Tambah'); }); /* |-------------------------------------------------------------------------- | Edge Cases |-------------------------------------------------------------------------- */ it('handles empty voucher list', function () { $component = mountIndexComponent($this->user); expect($component->stats[0]['value'])->toBe(0); expect($component->stats[1]['value'])->toBe(0); expect($component->stats[2]['value'])->toBe(0); expect($component->stats[3]['value'])->toBe(0); }); it('handles voucher with same start and end date', function () { $today = now()->format('Y-m-d'); Voucher::factory()->create([ 'start_date' => $today, 'end_date' => $today, ]); $component = mountIndexComponent($this->user); // Should be counted as active (start_date <= now AND end_date >= now) expect($component->stats[1]['value'])->toBe(1); }); it('updates stats when vouchers are deleted', function () { $voucher1 = Voucher::factory()->create([ 'start_date' => now()->subDay()->format('Y-m-d'), 'end_date' => now()->addDay()->format('Y-m-d'), ]); $voucher2 = Voucher::factory()->create([ 'start_date' => now()->addDay()->format('Y-m-d'), 'end_date' => now()->addWeek()->format('Y-m-d'), ]); $component = mountIndexComponent($this->user); expect($component->stats[0]['value'])->toBe(2); expect($component->stats[1]['value'])->toBe(1); expect($component->stats[3]['value'])->toBe(1); $component->call('delete', $voucher1); // Note: Stats are calculated in mount(), so they won't update automatically // This test verifies the delete works, but stats would need remount to update expect(Voucher::count())->toBe(1); });