libra/tests/Feature/Admin/AnalyticsChartsTest.php

273 lines
9.1 KiB
PHP

<?php
use App\Models\Consultation;
use App\Models\User;
use App\Services\AnalyticsService;
use Illuminate\Support\Facades\Cache;
use Livewire\Volt\Volt;
beforeEach(function () {
$this->admin = User::factory()->admin()->create();
Cache::flush();
});
// ===========================================
// Access Control Tests
// ===========================================
test('admin can view analytics charts section', function () {
$this->actingAs($this->admin)
->get(route('admin.dashboard'))
->assertSuccessful()
->assertSee(__('admin_metrics.analytics_charts'))
->assertSee(__('admin_metrics.monthly_trends'));
});
test('non-admin cannot access analytics charts', function () {
$client = User::factory()->individual()->create();
$this->actingAs($client)
->get(route('admin.dashboard'))
->assertForbidden();
});
// ===========================================
// AnalyticsService Unit Tests
// ===========================================
test('analytics service returns correct monthly client counts', function () {
// Create clients across different months
User::factory()->individual()->create([
'created_at' => now()->subMonths(2)->startOfMonth()->addDays(5),
]);
User::factory()->count(3)->individual()->create([
'created_at' => now()->subMonth()->startOfMonth()->addDays(5),
]);
User::factory()->count(2)->company()->create([
'created_at' => now()->startOfMonth()->addDays(5),
]);
$service = new AnalyticsService;
$data = $service->getMonthlyNewClients(now()->subMonths(2)->startOfMonth(), 3);
expect($data)->toBe([1, 3, 2]);
});
test('analytics service returns correct monthly consultation counts', function () {
Consultation::factory()->count(2)->create([
'booking_date' => now()->subMonths(2)->startOfMonth()->addDays(5),
]);
Consultation::factory()->count(4)->create([
'booking_date' => now()->subMonth()->startOfMonth()->addDays(5),
]);
Consultation::factory()->count(3)->create([
'booking_date' => now()->startOfMonth()->addDays(5),
]);
$service = new AnalyticsService;
$data = $service->getMonthlyConsultations(now()->subMonths(2)->startOfMonth(), 3);
expect($data)->toBe([2, 4, 3]);
});
test('consultation breakdown calculates free vs paid correctly', function () {
Consultation::factory()->count(5)->free()->create([
'booking_date' => now()->subMonth(),
]);
Consultation::factory()->count(3)->paid()->create([
'booking_date' => now()->subMonth(),
]);
$service = new AnalyticsService;
$breakdown = $service->getConsultationTypeBreakdown(now()->subYear()->startOfMonth(), 12);
expect($breakdown['free'])->toBe(5)
->and($breakdown['paid'])->toBe(3);
});
test('no-show rate calculates correctly', function () {
// Create 8 completed, 2 no-shows = 20% rate
Consultation::factory()->count(8)->completed()->create([
'booking_date' => now()->startOfMonth()->addDays(5),
]);
Consultation::factory()->count(2)->noShow()->create([
'booking_date' => now()->startOfMonth()->addDays(5),
]);
$service = new AnalyticsService;
$rates = $service->getMonthlyNoShowRates(now()->startOfMonth(), 1);
expect($rates[0])->toBe(20.0);
});
test('no-show rate returns zero when no consultations exist', function () {
$service = new AnalyticsService;
$rates = $service->getMonthlyNoShowRates(now()->startOfMonth(), 1);
expect($rates[0])->toBe(0);
});
test('no-show rate handles zero completed consultations', function () {
// Only create pending consultations (not completed or no-show)
Consultation::factory()->count(5)->pending()->create([
'booking_date' => now()->startOfMonth()->addDays(5),
]);
$service = new AnalyticsService;
$rates = $service->getMonthlyNoShowRates(now()->startOfMonth(), 1);
expect($rates[0])->toBe(0);
});
test('month labels are generated correctly', function () {
$service = new AnalyticsService;
$labels = $service->getMonthLabels(now()->startOfMonth(), 3);
expect($labels)->toHaveCount(3)
->and($labels[0])->toBe(now()->startOfMonth()->translatedFormat('M Y'))
->and($labels[1])->toBe(now()->addMonth()->translatedFormat('M Y'))
->and($labels[2])->toBe(now()->addMonths(2)->translatedFormat('M Y'));
});
// ===========================================
// Dashboard Component Chart Data Tests
// ===========================================
test('dashboard provides chart data', function () {
$this->actingAs($this->admin);
$component = Volt::test('admin.dashboard');
$chartData = $component->viewData('chartData');
expect($chartData)->toHaveKeys(['labels', 'newClients', 'consultations', 'consultationBreakdown', 'noShowRates'])
->and($chartData['consultationBreakdown'])->toHaveKeys(['free', 'paid']);
});
test('chart data defaults to 6 months', function () {
$this->actingAs($this->admin);
$component = Volt::test('admin.dashboard');
$chartData = $component->viewData('chartData');
expect($chartData['labels'])->toHaveCount(6)
->and($chartData['newClients'])->toHaveCount(6)
->and($chartData['consultations'])->toHaveCount(6)
->and($chartData['noShowRates'])->toHaveCount(6);
});
test('date range selector changes chart period to 12 months', function () {
$this->actingAs($this->admin);
$component = Volt::test('admin.dashboard')
->assertSet('chartPeriod', '6m')
->set('chartPeriod', '12m')
->assertSet('chartPeriod', '12m');
$chartData = $component->viewData('chartData');
expect($chartData['labels'])->toHaveCount(12)
->and($chartData['newClients'])->toHaveCount(12)
->and($chartData['consultations'])->toHaveCount(12)
->and($chartData['noShowRates'])->toHaveCount(12);
});
test('custom date range can be set', function () {
$this->actingAs($this->admin);
$start = now()->subMonths(3)->format('Y-m');
$end = now()->format('Y-m');
$component = Volt::test('admin.dashboard')
->set('customStartMonth', $start)
->set('customEndMonth', $end)
->call('setCustomRange')
->assertSet('chartPeriod', 'custom');
$chartData = $component->viewData('chartData');
expect($chartData['labels'])->toHaveCount(4); // 4 months inclusive
});
test('chart handles empty data gracefully', function () {
$this->actingAs($this->admin);
// No clients or consultations created
$component = Volt::test('admin.dashboard');
$chartData = $component->viewData('chartData');
// Should have all zeros but not error
expect(array_sum($chartData['newClients']))->toBe(0)
->and(array_sum($chartData['consultations']))->toBe(0)
->and($chartData['consultationBreakdown']['free'])->toBe(0)
->and($chartData['consultationBreakdown']['paid'])->toBe(0)
->and(array_sum($chartData['noShowRates']))->toBe(0);
});
// ===========================================
// UI Element Tests
// ===========================================
test('dashboard displays chart section with date range buttons', function () {
$this->actingAs($this->admin)
->get(route('admin.dashboard'))
->assertSee(__('admin_metrics.last_6_months'))
->assertSee(__('admin_metrics.last_12_months'))
->assertSee(__('admin_metrics.apply'));
});
test('dashboard displays all chart cards', function () {
$this->actingAs($this->admin)
->get(route('admin.dashboard'))
->assertSee(__('admin_metrics.monthly_trends'))
->assertSee(__('admin_metrics.consultation_breakdown'))
->assertSee(__('admin_metrics.noshow_rate_trend'));
});
test('empty state message shown when no data', function () {
$this->actingAs($this->admin)
->get(route('admin.dashboard'))
->assertSee(__('admin_metrics.no_data_available'));
});
// ===========================================
// Data Accuracy Tests
// ===========================================
test('chart data excludes admin users from client counts', function () {
// Create admin users (should not be counted)
User::factory()->count(2)->admin()->create([
'created_at' => now()->startOfMonth()->addDays(5),
]);
// Create individual clients (should be counted)
User::factory()->count(3)->individual()->create([
'created_at' => now()->startOfMonth()->addDays(5),
]);
$service = new AnalyticsService;
$data = $service->getMonthlyNewClients(now()->startOfMonth(), 1);
expect($data[0])->toBe(3);
});
test('chart data correctly filters by date range', function () {
// Create consultations outside the range
Consultation::factory()->count(5)->create([
'booking_date' => now()->subYears(2),
]);
// Create consultations inside the range
Consultation::factory()->count(3)->create([
'booking_date' => now()->subMonth()->startOfMonth()->addDays(5),
]);
$service = new AnalyticsService;
$data = $service->getMonthlyConsultations(now()->subMonths(2)->startOfMonth(), 3);
// Only the 3 consultations in range should be counted
expect(array_sum($data))->toBe(3);
});