273 lines
9.1 KiB
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);
|
|
});
|