complete story 6.7 with QA tests and generated epic 10 to fix the branding colors
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Consultation;
|
||||
use App\Models\Post;
|
||||
use App\Models\Timeline;
|
||||
use App\Models\TimelineUpdate;
|
||||
use App\Models\User;
|
||||
use App\Services\MonthlyReportService;
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->admin = User::factory()->admin()->create();
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Access Tests
|
||||
// ===========================================
|
||||
|
||||
test('admin can access monthly report page', function () {
|
||||
$this->actingAs($this->admin)
|
||||
->get(route('admin.reports.monthly'))
|
||||
->assertOk()
|
||||
->assertSee(__('report.monthly_report'));
|
||||
});
|
||||
|
||||
test('non-admin cannot access monthly report page', function () {
|
||||
$client = User::factory()->individual()->create();
|
||||
|
||||
$this->actingAs($client)
|
||||
->get(route('admin.reports.monthly'))
|
||||
->assertForbidden();
|
||||
});
|
||||
|
||||
test('unauthenticated user cannot access monthly report page', function () {
|
||||
$this->get(route('admin.reports.monthly'))
|
||||
->assertRedirect(route('login'));
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Component Tests
|
||||
// ===========================================
|
||||
|
||||
test('monthly report component mounts with previous month as default', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
$previousMonth = now()->subMonth();
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->assertSet('selectedPeriod', $previousMonth->format('Y-m'));
|
||||
});
|
||||
|
||||
test('available months shows only last 12 months', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
$component = Volt::test('admin.reports.monthly-report');
|
||||
|
||||
$availableMonths = $component->get('availableMonths');
|
||||
|
||||
expect(count($availableMonths))->toBe(12);
|
||||
});
|
||||
|
||||
test('available months does not include current month', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
$component = Volt::test('admin.reports.monthly-report');
|
||||
|
||||
$availableMonths = $component->get('availableMonths');
|
||||
$currentMonth = now()->format('Y-m');
|
||||
|
||||
$values = array_column($availableMonths, 'value');
|
||||
|
||||
expect($values)->not->toContain($currentMonth);
|
||||
});
|
||||
|
||||
test('available months includes previous month', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
$component = Volt::test('admin.reports.monthly-report');
|
||||
|
||||
$availableMonths = $component->get('availableMonths');
|
||||
$previousMonth = now()->subMonth()->format('Y-m');
|
||||
|
||||
$values = array_column($availableMonths, 'value');
|
||||
|
||||
expect($values)->toContain($previousMonth);
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// PDF Generation Tests
|
||||
// ===========================================
|
||||
|
||||
test('monthly report generates valid PDF', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
// Create test data for the month
|
||||
$targetMonth = now()->subMonth();
|
||||
User::factory()->count(5)->individual()->create([
|
||||
'created_at' => $targetMonth,
|
||||
]);
|
||||
Consultation::factory()->count(10)->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->set('selectedPeriod', $targetMonth->format('Y-m'))
|
||||
->call('generate')
|
||||
->assertFileDownloaded("monthly-report-{$targetMonth->year}-{$targetMonth->month}.pdf");
|
||||
});
|
||||
|
||||
test('report handles month with no data gracefully', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
// Generate for a month with no data (far in the past)
|
||||
$emptyMonth = now()->subMonths(10);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->set('selectedPeriod', $emptyMonth->format('Y-m'))
|
||||
->call('generate')
|
||||
->assertFileDownloaded();
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Service Tests - User Statistics
|
||||
// ===========================================
|
||||
|
||||
test('user statistics are accurate for selected month', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
// Create 3 users in target month
|
||||
User::factory()->count(3)->individual()->create([
|
||||
'created_at' => $targetMonth,
|
||||
]);
|
||||
|
||||
// Create 2 users in different month (should not be counted as new)
|
||||
User::factory()->count(2)->individual()->create([
|
||||
'created_at' => now()->subMonths(3),
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getUserStats($startDate, $endDate);
|
||||
|
||||
expect($stats['new_clients'])->toBe(3);
|
||||
});
|
||||
|
||||
test('user statistics count individual and company clients separately', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
User::factory()->count(4)->individual()->create([
|
||||
'created_at' => $targetMonth->copy()->subMonth(),
|
||||
]);
|
||||
User::factory()->count(2)->company()->create([
|
||||
'created_at' => $targetMonth->copy()->subMonth(),
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getUserStats($startDate, $endDate);
|
||||
|
||||
expect($stats['individual'])->toBe(4);
|
||||
expect($stats['company'])->toBe(2);
|
||||
expect($stats['total_active'])->toBe(6);
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Service Tests - Consultation Statistics
|
||||
// ===========================================
|
||||
|
||||
test('consultation statistics calculate totals correctly', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
Consultation::factory()->count(5)->completed()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
Consultation::factory()->count(3)->pending()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
Consultation::factory()->count(2)->cancelled()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getConsultationStats($startDate, $endDate);
|
||||
|
||||
expect($stats['total'])->toBe(10);
|
||||
expect($stats['completed'])->toBe(5);
|
||||
expect($stats['cancelled'])->toBe(2);
|
||||
});
|
||||
|
||||
test('consultation statistics calculate no-show rate correctly', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
// 8 completed + 2 no-shows = 20% no-show rate
|
||||
Consultation::factory()->count(8)->completed()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
Consultation::factory()->count(2)->noShow()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getConsultationStats($startDate, $endDate);
|
||||
|
||||
expect($stats['no_show_rate'])->toBe(20.0);
|
||||
});
|
||||
|
||||
test('consultation statistics handle zero completed consultations', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
// Only pending consultations, no completed ones
|
||||
Consultation::factory()->count(5)->pending()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getConsultationStats($startDate, $endDate);
|
||||
|
||||
expect($stats['no_show_rate'])->toBe(0);
|
||||
});
|
||||
|
||||
test('consultation statistics count free and paid types', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
Consultation::factory()->count(4)->free()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
Consultation::factory()->count(6)->paid()->create([
|
||||
'booking_date' => $targetMonth,
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getConsultationStats($startDate, $endDate);
|
||||
|
||||
expect($stats['free'])->toBe(4);
|
||||
expect($stats['paid'])->toBe(6);
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Service Tests - Timeline Statistics
|
||||
// ===========================================
|
||||
|
||||
test('timeline statistics count active timelines correctly', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
Timeline::factory()->count(5)->active()->create([
|
||||
'created_at' => $targetMonth->copy()->subMonth(),
|
||||
]);
|
||||
Timeline::factory()->count(2)->archived()->create([
|
||||
'created_at' => $targetMonth->copy()->subMonth(),
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getTimelineStats($startDate, $endDate);
|
||||
|
||||
expect($stats['active'])->toBe(5);
|
||||
});
|
||||
|
||||
test('timeline statistics count new timelines in month', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
Timeline::factory()->count(3)->create([
|
||||
'created_at' => $targetMonth,
|
||||
]);
|
||||
Timeline::factory()->count(2)->create([
|
||||
'created_at' => now()->subMonths(3),
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getTimelineStats($startDate, $endDate);
|
||||
|
||||
expect($stats['new'])->toBe(3);
|
||||
});
|
||||
|
||||
test('timeline statistics count updates in month', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
$timeline = Timeline::factory()->create();
|
||||
TimelineUpdate::factory()->count(5)->create([
|
||||
'timeline_id' => $timeline->id,
|
||||
'created_at' => $targetMonth,
|
||||
]);
|
||||
TimelineUpdate::factory()->count(2)->create([
|
||||
'timeline_id' => $timeline->id,
|
||||
'created_at' => now()->subMonths(3),
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getTimelineStats($startDate, $endDate);
|
||||
|
||||
expect($stats['updates'])->toBe(5);
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Service Tests - Post Statistics
|
||||
// ===========================================
|
||||
|
||||
test('post statistics count published posts in month', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$startDate = $targetMonth->copy()->startOfMonth();
|
||||
$endDate = $targetMonth->copy()->endOfMonth();
|
||||
|
||||
Post::factory()->count(4)->published()->create([
|
||||
'published_at' => $targetMonth,
|
||||
]);
|
||||
Post::factory()->count(2)->published()->create([
|
||||
'published_at' => now()->subMonths(3),
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$stats = $service->getPostStats($startDate, $endDate);
|
||||
|
||||
expect($stats['this_month'])->toBe(4);
|
||||
expect($stats['total'])->toBe(6);
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Service Tests - Previous Month Comparison
|
||||
// ===========================================
|
||||
|
||||
test('previous month comparison returns null when no prior data exists', function () {
|
||||
$targetMonth = now()->subMonth()->startOfMonth();
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$comparison = $service->getPreviousMonthComparison($targetMonth);
|
||||
|
||||
expect($comparison)->toBeNull();
|
||||
});
|
||||
|
||||
test('previous month comparison returns data when prior month has data', function () {
|
||||
$targetMonth = now()->subMonth();
|
||||
$previousMonth = now()->subMonths(2);
|
||||
|
||||
// Create data in the previous month
|
||||
User::factory()->count(3)->individual()->create([
|
||||
'created_at' => $previousMonth,
|
||||
]);
|
||||
Consultation::factory()->count(5)->create([
|
||||
'booking_date' => $previousMonth,
|
||||
]);
|
||||
|
||||
$service = new MonthlyReportService;
|
||||
$comparison = $service->getPreviousMonthComparison($targetMonth->startOfMonth());
|
||||
|
||||
expect($comparison)->not->toBeNull();
|
||||
expect($comparison['consultations'])->toBe(5);
|
||||
expect($comparison['clients'])->toBe(3);
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// Language Tests
|
||||
// ===========================================
|
||||
|
||||
test('report respects admin language preference for Arabic', function () {
|
||||
$adminArabic = User::factory()->admin()->create(['preferred_language' => 'ar']);
|
||||
|
||||
$this->actingAs($adminArabic);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->assertSee(__('report.monthly_report', [], 'ar'));
|
||||
});
|
||||
|
||||
test('report respects admin language preference for English', function () {
|
||||
$adminEnglish = User::factory()->admin()->create(['preferred_language' => 'en']);
|
||||
|
||||
app()->setLocale('en');
|
||||
$this->actingAs($adminEnglish);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->assertSee('Monthly Statistics Report');
|
||||
});
|
||||
|
||||
// ===========================================
|
||||
// UI Elements Tests
|
||||
// ===========================================
|
||||
|
||||
test('monthly report page shows table of contents preview', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->assertSee(__('report.table_of_contents'))
|
||||
->assertSee(__('report.executive_summary'))
|
||||
->assertSee(__('report.user_statistics'))
|
||||
->assertSee(__('report.consultation_statistics'))
|
||||
->assertSee(__('report.timeline_statistics'))
|
||||
->assertSee(__('report.post_statistics'))
|
||||
->assertSee(__('report.trends_chart'));
|
||||
});
|
||||
|
||||
test('monthly report page shows generate button', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->assertSee(__('report.generate'));
|
||||
});
|
||||
|
||||
test('monthly report page shows period selector', function () {
|
||||
$this->actingAs($this->admin);
|
||||
|
||||
Volt::test('admin.reports.monthly-report')
|
||||
->assertSee(__('report.select_period'));
|
||||
});
|
||||
Reference in New Issue
Block a user