admin = User::factory()->admin()->create(); }); // =========================================== // Access Tests // =========================================== test('admin can access timeline export page', function () { $this->actingAs($this->admin) ->get(route('admin.timelines.export')) ->assertOk(); }); test('non-admin cannot access timeline export page', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('admin.timelines.export')) ->assertForbidden(); }); test('unauthenticated user cannot access timeline export page', function () { $this->get(route('admin.timelines.export')) ->assertRedirect(route('login')); }); // =========================================== // CSV Export Tests // =========================================== test('admin can export all timelines as CSV', function () { Timeline::factory()->count(5)->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'all') ->call('exportCsv') ->assertFileDownloaded('timelines-export-'.now()->format('Y-m-d').'.csv'); }); test('admin can export timelines filtered by active status', function () { Timeline::factory()->count(3)->active()->create(); Timeline::factory()->count(2)->archived()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'active') ->call('exportCsv') ->assertFileDownloaded(); }); test('admin can export timelines filtered by archived status', function () { Timeline::factory()->count(3)->active()->create(); Timeline::factory()->count(2)->archived()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'archived') ->call('exportCsv') ->assertFileDownloaded(); }); test('admin can export timelines filtered by client', function () { $client1 = User::factory()->individual()->create(); $client2 = User::factory()->individual()->create(); Timeline::factory()->count(3)->create(['user_id' => $client1->id]); Timeline::factory()->count(2)->create(['user_id' => $client2->id]); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('clientId', $client1->id) ->call('exportCsv') ->assertFileDownloaded(); }); test('admin can export timelines filtered by date range', function () { Timeline::factory()->create(['created_at' => now()->subDays(10)]); Timeline::factory()->create(['created_at' => now()->subDays(5)]); Timeline::factory()->create(['created_at' => now()]); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('dateFrom', now()->subDays(7)->format('Y-m-d')) ->set('dateTo', now()->format('Y-m-d')) ->call('exportCsv') ->assertFileDownloaded(); }); test('admin can export with combined filters', function () { $client = User::factory()->individual()->create(); Timeline::factory()->active()->create(['user_id' => $client->id, 'created_at' => now()->subDays(5)]); Timeline::factory()->archived()->create(['user_id' => $client->id, 'created_at' => now()->subDays(5)]); Timeline::factory()->active()->create(['created_at' => now()->subDays(5)]); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('clientId', $client->id) ->set('status', 'active') ->set('dateFrom', now()->subDays(7)->format('Y-m-d')) ->set('dateTo', now()->format('Y-m-d')) ->call('exportCsv') ->assertFileDownloaded(); }); // =========================================== // PDF Export Tests // =========================================== test('admin can export timelines as PDF', function () { Timeline::factory()->count(5)->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->call('exportPdf') ->assertFileDownloaded('timelines-export-'.now()->format('Y-m-d').'.pdf'); }); test('admin can export PDF with filters', function () { Timeline::factory()->count(3)->active()->create(); Timeline::factory()->count(2)->archived()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'active') ->call('exportPdf') ->assertFileDownloaded(); }); test('include updates toggle adds update content to PDF', function () { $timeline = Timeline::factory()->create(); TimelineUpdate::factory()->count(3)->create(['timeline_id' => $timeline->id]); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('includeUpdates', true) ->call('exportPdf') ->assertFileDownloaded(); }); // =========================================== // Empty Dataset Tests // =========================================== test('CSV export dispatches notification when no timelines match filters', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'archived') ->call('exportCsv') ->assertDispatched('notify'); }); test('PDF export dispatches notification when no timelines match filters', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'archived') ->call('exportPdf') ->assertDispatched('notify'); }); test('preview count shows zero when no timelines match filters', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->set('status', 'archived') ->assertSee('0'); }); // =========================================== // Filter Tests // =========================================== test('preview count updates when filters change', function () { Timeline::factory()->count(3)->active()->create(); Timeline::factory()->count(2)->archived()->create(); $this->actingAs($this->admin); // All timelines - should show 5 Volt::test('admin.timelines.export-timelines') ->set('status', 'all') ->assertSeeHtml('5'); // Filter to active only - should show 3 Volt::test('admin.timelines.export-timelines') ->set('status', 'active') ->assertSeeHtml('3'); // Filter to archived only - should show 2 Volt::test('admin.timelines.export-timelines') ->set('status', 'archived') ->assertSeeHtml('2'); }); test('clear filters resets all filter values', function () { $client = User::factory()->individual()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.export-timelines') ->set('clientId', $client->id) ->set('clientSearch', $client->full_name) ->set('status', 'archived') ->set('dateFrom', '2024-01-01') ->set('dateTo', '2024-12-31') ->set('includeUpdates', true) ->call('clearFilters'); expect($component->get('clientId'))->toBeNull(); expect($component->get('clientSearch'))->toBe(''); expect($component->get('status'))->toBe('all'); expect($component->get('dateFrom'))->toBe(''); expect($component->get('dateTo'))->toBe(''); expect($component->get('includeUpdates'))->toBeFalse(); }); // =========================================== // Client Search Tests // =========================================== test('client search returns matching clients', function () { $client1 = User::factory()->individual()->create(['full_name' => 'John Doe']); $client2 = User::factory()->individual()->create(['full_name' => 'Jane Smith']); User::factory()->admin()->create(['full_name' => 'Admin User']); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.export-timelines') ->set('clientSearch', 'John'); expect($component->get('clients'))->toHaveCount(1); expect($component->get('clients')->first()->id)->toBe($client1->id); }); test('client search excludes admin users', function () { User::factory()->admin()->create(['full_name' => 'Admin John']); $client = User::factory()->individual()->create(['full_name' => 'Client John']); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.export-timelines') ->set('clientSearch', 'John'); expect($component->get('clients'))->toHaveCount(1); expect($component->get('clients')->first()->id)->toBe($client->id); }); test('client search requires minimum 2 characters', function () { User::factory()->individual()->create(['full_name' => 'John Doe']); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.export-timelines') ->set('clientSearch', 'J'); expect($component->get('clients'))->toBeEmpty(); }); test('select client sets client ID and search field', function () { $client = User::factory()->individual()->create(['full_name' => 'John Doe']); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.export-timelines') ->call('selectClient', $client->id); expect($component->get('clientId'))->toBe($client->id); expect($component->get('clientSearch'))->toBe('John Doe'); }); test('clear client resets client filter', function () { $client = User::factory()->individual()->create(); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.export-timelines') ->set('clientId', $client->id) ->set('clientSearch', $client->full_name) ->call('clearClient'); expect($component->get('clientId'))->toBeNull(); expect($component->get('clientSearch'))->toBe(''); }); // =========================================== // Timeline Data Tests // =========================================== test('timeline without updates shows zero count', function () { Timeline::factory()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->call('exportCsv') ->assertFileDownloaded(); }); test('timeline without case reference shows dash in export', function () { Timeline::factory()->create(['case_reference' => null]); $this->actingAs($this->admin); Volt::test('admin.timelines.export-timelines') ->call('exportCsv') ->assertFileDownloaded(); }); // =========================================== // Language Tests // =========================================== test('export uses admin preferred language for headers', function () { $adminArabic = User::factory()->admin()->create(['preferred_language' => 'ar']); Timeline::factory()->create(); $this->actingAs($adminArabic); Volt::test('admin.timelines.export-timelines') ->call('exportCsv') ->assertFileDownloaded(); }); test('export uses English when admin prefers English', function () { $adminEnglish = User::factory()->admin()->create(['preferred_language' => 'en']); Timeline::factory()->create(); $this->actingAs($adminEnglish); Volt::test('admin.timelines.export-timelines') ->call('exportCsv') ->assertFileDownloaded(); });