admin = User::factory()->admin()->create(); }); // =========================================== // Access Tests // =========================================== test('admin can access blocked times page', function () { $this->actingAs($this->admin) ->get(route('admin.settings.blocked-times')) ->assertOk(); }); test('non-admin cannot access blocked times page', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('admin.settings.blocked-times')) ->assertForbidden(); }); test('unauthenticated user cannot access blocked times page', function () { $this->get(route('admin.settings.blocked-times')) ->assertRedirect(route('login')); }); // =========================================== // Create All-Day Block Tests // =========================================== test('admin can create an all-day block', function () { $this->actingAs($this->admin); $blockDate = today()->addDays(5)->format('Y-m-d'); $initialCount = BlockedTime::count(); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', $blockDate) ->set('is_all_day', true) ->set('reason', 'Holiday') ->call('save') ->assertHasNoErrors(); expect(BlockedTime::count())->toBe($initialCount + 1); $blocked = BlockedTime::latest()->first(); expect($blocked)->not->toBeNull() ->and($blocked->isAllDay())->toBeTrue() ->and($blocked->reason)->toBe('Holiday'); }); // =========================================== // Create Time-Range Block Tests // =========================================== test('admin can create a time-range block', function () { $this->actingAs($this->admin); $initialCount = BlockedTime::count(); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->addDays(3)->format('Y-m-d')) ->set('is_all_day', false) ->set('start_time', '09:00') ->set('end_time', '12:00') ->set('reason', 'Morning meeting') ->call('save') ->assertHasNoErrors(); expect(BlockedTime::count())->toBe($initialCount + 1); $blocked = BlockedTime::latest()->first(); expect($blocked)->not->toBeNull() ->and($blocked->isAllDay())->toBeFalse() ->and($blocked->start_time)->toContain('09:00') ->and($blocked->end_time)->toContain('12:00') ->and($blocked->reason)->toBe('Morning meeting'); }); test('admin can create block without reason', function () { $this->actingAs($this->admin); $initialCount = BlockedTime::count(); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->addDays(2)->format('Y-m-d')) ->set('is_all_day', true) ->set('reason', '') ->call('save') ->assertHasNoErrors(); expect(BlockedTime::count())->toBe($initialCount + 1); $blocked = BlockedTime::latest()->first(); expect($blocked)->not->toBeNull() ->and($blocked->reason)->toBeNull(); }); // =========================================== // Edit Blocked Time Tests // =========================================== test('admin can edit an existing blocked time', function () { $blocked = BlockedTime::factory()->allDay()->create([ 'block_date' => today()->addDays(5), 'reason' => 'Original reason', ]); $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openEditModal', $blocked->id) ->set('reason', 'Updated reason') ->call('save') ->assertHasNoErrors(); $blocked->refresh(); expect($blocked->reason)->toBe('Updated reason'); }); test('admin can change block from all-day to time-range', function () { $blocked = BlockedTime::factory()->allDay()->create([ 'block_date' => today()->addDays(5), ]); $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openEditModal', $blocked->id) ->set('is_all_day', false) ->set('start_time', '14:00') ->set('end_time', '18:00') ->call('save') ->assertHasNoErrors(); $blocked->refresh(); expect($blocked->isAllDay())->toBeFalse() ->and($blocked->start_time)->toContain('14:00') ->and($blocked->end_time)->toContain('18:00'); }); test('admin can edit past blocked time date', function () { $blocked = BlockedTime::factory()->past()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->call('openEditModal', $blocked->id) ->set('reason', 'Updated past block') ->call('save') ->assertHasNoErrors(); $blocked->refresh(); expect($blocked->reason)->toBe('Updated past block'); }); // =========================================== // Delete Blocked Time Tests // =========================================== test('admin can delete a blocked time', function () { $blocked = BlockedTime::factory()->create(); $blockedId = $blocked->id; $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('confirmDelete', $blockedId) ->call('delete') ->assertHasNoErrors(); expect(BlockedTime::find($blockedId))->toBeNull(); }); // =========================================== // Validation Tests // =========================================== test('cannot create block with end time before start time', function () { $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->addDays(5)->format('Y-m-d')) ->set('is_all_day', false) ->set('start_time', '14:00') ->set('end_time', '10:00') ->call('save') ->assertHasErrors(['end_time']); }); test('cannot create block for past dates', function () { $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->subDays(5)->format('Y-m-d')) ->set('is_all_day', true) ->call('save') ->assertHasErrors(['block_date']); }); test('can create block for today', function () { $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->format('Y-m-d')) ->set('is_all_day', true) ->call('save') ->assertHasNoErrors(); expect(BlockedTime::where('block_date', today())->exists())->toBeTrue(); }); test('reason field respects 255 character max length', function () { $this->actingAs($this->admin); $longReason = str_repeat('a', 256); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->addDays(5)->format('Y-m-d')) ->set('is_all_day', true) ->set('reason', $longReason) ->call('save') ->assertHasErrors(['reason']); }); // =========================================== // List View Tests // =========================================== test('list displays all blocked times sorted by date', function () { BlockedTime::factory()->create(['block_date' => today()->addDays(10)]); BlockedTime::factory()->create(['block_date' => today()->addDays(2)]); BlockedTime::factory()->create(['block_date' => today()->addDays(5)]); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->set('filter', 'all'); $blockedTimes = $component->viewData('blockedTimes'); expect($blockedTimes)->toHaveCount(3); }); test('filter by upcoming shows only future blocks', function () { BlockedTime::factory()->upcoming()->create(); BlockedTime::factory()->today()->create(); BlockedTime::factory()->past()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->set('filter', 'upcoming'); $blockedTimes = $component->viewData('blockedTimes'); expect($blockedTimes)->toHaveCount(2) ->and($blockedTimes->every(fn ($b) => $b->block_date->gte(today())))->toBeTrue(); }); test('filter by past shows only past blocks', function () { BlockedTime::factory()->upcoming()->create(); BlockedTime::factory()->today()->create(); BlockedTime::factory()->past()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->set('filter', 'past'); $blockedTimes = $component->viewData('blockedTimes'); expect($blockedTimes)->toHaveCount(1) ->and($blockedTimes->every(fn ($b) => $b->block_date->lt(today())))->toBeTrue(); }); test('filter by all shows all blocks', function () { BlockedTime::factory()->upcoming()->create(); BlockedTime::factory()->today()->create(); BlockedTime::factory()->past()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->set('filter', 'all'); $blockedTimes = $component->viewData('blockedTimes'); expect($blockedTimes)->toHaveCount(3); }); test('list shows reason if provided', function () { BlockedTime::factory()->create([ 'block_date' => today()->addDays(5), 'reason' => 'Personal vacation', ]); $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->assertSee('Personal vacation'); }); // =========================================== // Audit Log Tests // =========================================== test('audit log is created when blocked time is created', function () { $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', today()->addDays(5)->format('Y-m-d')) ->set('is_all_day', true) ->set('reason', 'Test') ->call('save'); $log = AdminLog::where('target_type', 'blocked_time')->first(); expect($log)->not->toBeNull() ->and($log->admin_id)->toBe($this->admin->id) ->and($log->action)->toBe('create'); }); test('audit log is created when blocked time is updated', function () { $blocked = BlockedTime::factory()->create([ 'block_date' => today()->addDays(5), ]); $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openEditModal', $blocked->id) ->set('reason', 'Updated') ->call('save'); $log = AdminLog::where('target_type', 'blocked_time') ->where('action', 'update') ->first(); expect($log)->not->toBeNull() ->and($log->target_id)->toBe($blocked->id); }); test('audit log is created when blocked time is deleted', function () { $blocked = BlockedTime::factory()->create(); $blockedId = $blocked->id; $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('confirmDelete', $blockedId) ->call('delete'); $log = AdminLog::where('target_type', 'blocked_time') ->where('action', 'delete') ->first(); expect($log)->not->toBeNull() ->and($log->target_id)->toBe($blockedId); }); // =========================================== // Pending Booking Warning Tests // =========================================== test('warning displayed when blocking date with pending consultations', function () { $client = User::factory()->individual()->create(); $blockDate = today()->addDays(5); Consultation::factory()->create([ 'user_id' => $client->id, 'booking_date' => $blockDate, 'booking_time' => '10:00:00', 'status' => ConsultationStatus::Pending, ]); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', $blockDate->format('Y-m-d')) ->set('is_all_day', true) ->call('checkPendingBookings'); expect($component->get('pendingBookings'))->toHaveCount(1) ->and($component->get('pendingBookings')[0]['client'])->toBe($client->full_name); }); test('warning shows pending bookings within time range', function () { $client = User::factory()->individual()->create(); $blockDate = today()->addDays(5); // Consultation within blocked range (10:00 is between 09:00 and 12:00) Consultation::factory()->create([ 'user_id' => $client->id, 'booking_date' => $blockDate, 'booking_time' => '10:00:00', 'status' => ConsultationStatus::Pending, ]); // Consultation outside blocked range (15:00 is not between 09:00 and 12:00) Consultation::factory()->create([ 'user_id' => $client->id, 'booking_date' => $blockDate, 'booking_time' => '15:00:00', 'status' => ConsultationStatus::Pending, ]); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', $blockDate->format('Y-m-d')) ->set('is_all_day', false) ->set('start_time', '09:00') ->set('end_time', '12:00') ->call('checkPendingBookings'); expect($component->get('pendingBookings'))->toHaveCount(1); }); // =========================================== // Modal Tests // =========================================== test('create modal opens with default values', function () { $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->call('openCreateModal'); expect($component->get('showModal'))->toBeTrue() ->and($component->get('editingId'))->toBeNull() ->and($component->get('is_all_day'))->toBeTrue() ->and($component->get('block_date'))->toBe(today()->format('Y-m-d')); }); test('edit modal opens with existing values', function () { $blocked = BlockedTime::factory()->timeRange('10:00', '14:00')->create([ 'block_date' => today()->addDays(5), 'reason' => 'Test reason', ]); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->call('openEditModal', $blocked->id); expect($component->get('showModal'))->toBeTrue() ->and($component->get('editingId'))->toBe($blocked->id) ->and($component->get('is_all_day'))->toBeFalse() ->and($component->get('start_time'))->toBe('10:00') ->and($component->get('end_time'))->toBe('14:00') ->and($component->get('reason'))->toBe('Test reason'); }); test('modal closes and resets on close', function () { $blocked = BlockedTime::factory()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.settings.blocked-times') ->call('openEditModal', $blocked->id) ->call('closeModal'); expect($component->get('showModal'))->toBeFalse() ->and($component->get('editingId'))->toBeNull(); }); // =========================================== // Multiple Blocks on Same Date Tests // =========================================== test('multiple time-range blocks on same date handled correctly', function () { $blockDate = today()->addDays(5); BlockedTime::factory()->timeRange('09:00', '12:00')->create([ 'block_date' => $blockDate, ]); $this->actingAs($this->admin); Volt::test('admin.settings.blocked-times') ->call('openCreateModal') ->set('block_date', $blockDate->format('Y-m-d')) ->set('is_all_day', false) ->set('start_time', '14:00') ->set('end_time', '17:00') ->call('save') ->assertHasNoErrors(); expect(BlockedTime::where('block_date', $blockDate)->count())->toBe(2); });