complete story 6.2 with qa tests
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
<?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);
|
||||
});
|
||||
Reference in New Issue
Block a user