admin = User::factory()->admin()->create(); }); // =========================================== // Route Access Tests // =========================================== test('admin can view timeline dashboard', function () { Timeline::factory()->count(5)->create(); $this->actingAs($this->admin) ->get(route('admin.timelines.index')) ->assertOk() ->assertSeeLivewire('admin.timelines.index'); }); test('non-admin cannot access timeline dashboard', function () { $client = User::factory()->individual()->create(); $this->actingAs($client) ->get(route('admin.timelines.index')) ->assertForbidden(); }); test('guest cannot access timeline dashboard', function () { $this->get(route('admin.timelines.index')) ->assertRedirect(route('login')); }); // =========================================== // List View Tests // =========================================== test('timeline dashboard displays all timelines with correct data', function () { $client = User::factory()->individual()->create(['full_name' => 'Test Client']); $timeline = Timeline::factory()->create([ 'user_id' => $client->id, 'case_name' => 'Test Case Name', ]); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->assertSee('Test Case Name') ->assertSee('Test Client'); }); test('dashboard shows empty state when no timelines exist', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->assertSee(__('timelines.no_timelines')); }); test('dashboard displays timeline status correctly', function () { $activeTimeline = Timeline::factory()->active()->create(['case_name' => 'Active Case']); $archivedTimeline = Timeline::factory()->archived()->create(['case_name' => 'Archived Case']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->assertSee('Active Case') ->assertSee('Archived Case') ->assertSee(__('enums.timeline_status.active')) ->assertSee(__('enums.timeline_status.archived')); }); test('dashboard displays update count', function () { $timeline = Timeline::factory()->create(); TimelineUpdate::factory()->count(3)->create(['timeline_id' => $timeline->id]); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.index'); // The component should have the timeline with 3 updates $timelines = $component->viewData('timelines'); expect($timelines->first()->updates_count)->toBe(3); }); // =========================================== // Filtering Tests // =========================================== test('can filter timelines by status', function () { Timeline::factory()->active()->create(['case_name' => 'Active Case']); Timeline::factory()->archived()->create(['case_name' => 'Archived Case']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('statusFilter', 'active') ->assertSee('Active Case') ->assertDontSee('Archived Case'); }); test('can filter timelines by client', function () { $client1 = User::factory()->individual()->create(['full_name' => 'Client One']); $client2 = User::factory()->individual()->create(['full_name' => 'Client Two']); Timeline::factory()->create(['user_id' => $client1->id, 'case_name' => 'Client1 Case']); Timeline::factory()->create(['user_id' => $client2->id, 'case_name' => 'Client2 Case']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('clientFilter', $client1->id) ->assertSee('Client1 Case') ->assertDontSee('Client2 Case'); }); test('can search timelines by case name', function () { Timeline::factory()->create(['case_name' => 'Contract Dispute']); Timeline::factory()->create(['case_name' => 'Property Issue']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('search', 'Contract') ->assertSee('Contract Dispute') ->assertDontSee('Property Issue'); }); test('can search timelines by case reference', function () { Timeline::factory()->create(['case_reference' => 'REF-123', 'case_name' => 'Case A']); Timeline::factory()->create(['case_reference' => 'REF-456', 'case_name' => 'Case B']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('search', 'REF-123') ->assertSee('Case A') ->assertDontSee('Case B'); }); test('can filter timelines by date range', function () { $oldTimeline = Timeline::factory()->create([ 'case_name' => 'Old Case', 'created_at' => now()->subDays(30), ]); $newTimeline = Timeline::factory()->create([ 'case_name' => 'New Case', 'created_at' => now(), ]); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('dateFrom', now()->subDays(5)->format('Y-m-d')) ->assertSee('New Case') ->assertDontSee('Old Case'); }); test('clear filters resets all filter values', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('search', 'test') ->set('statusFilter', 'active') ->set('clientFilter', '1') ->set('dateFrom', '2024-01-01') ->set('dateTo', '2024-12-31') ->call('clearFilters') ->assertSet('search', '') ->assertSet('statusFilter', '') ->assertSet('clientFilter', '') ->assertSet('dateFrom', '') ->assertSet('dateTo', ''); }); test('shows no results message when filters return empty', function () { Timeline::factory()->create(['case_name' => 'Existing Case']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('search', 'NonexistentCase') ->assertSee(__('timelines.no_timelines')); }); // =========================================== // Sorting Tests // =========================================== test('can sort timelines by case name', function () { Timeline::factory()->create(['case_name' => 'Zebra Case']); Timeline::factory()->create(['case_name' => 'Alpha Case']); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('sort', 'case_name') ->assertSet('sortBy', 'case_name') ->assertSet('sortDir', 'asc'); }); test('clicking same sort column toggles direction', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('sort', 'case_name') ->assertSet('sortDir', 'asc') ->call('sort', 'case_name') ->assertSet('sortDir', 'desc'); }); test('can sort timelines by updated_at', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('sort', 'updated_at') ->assertSet('sortBy', 'updated_at') ->assertSet('sortDir', 'asc'); }); test('can sort timelines by created_at', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('sort', 'created_at') ->assertSet('sortBy', 'created_at') ->assertSet('sortDir', 'asc'); }); test('can sort timelines by client', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('sort', 'user_id') ->assertSet('sortBy', 'user_id') ->assertSet('sortDir', 'asc'); }); test('default sort is by updated_at desc', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->assertSet('sortBy', 'updated_at') ->assertSet('sortDir', 'desc'); }); // =========================================== // Quick Actions Tests // =========================================== test('can archive timeline from dashboard', function () { $timeline = Timeline::factory()->active()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('toggleArchive', $timeline->id); expect($timeline->fresh()->status)->toBe(TimelineStatus::Archived); }); test('can unarchive timeline from dashboard', function () { $timeline = Timeline::factory()->archived()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('toggleArchive', $timeline->id); expect($timeline->fresh()->status)->toBe(TimelineStatus::Active); }); test('toggle archive creates admin log entry for archive', function () { $timeline = Timeline::factory()->active()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('toggleArchive', $timeline->id); $this->assertDatabaseHas('admin_logs', [ 'admin_id' => $this->admin->id, 'action' => 'archive', 'target_type' => 'timeline', 'target_id' => $timeline->id, ]); }); test('toggle archive creates admin log entry for unarchive', function () { $timeline = Timeline::factory()->archived()->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->call('toggleArchive', $timeline->id); $this->assertDatabaseHas('admin_logs', [ 'admin_id' => $this->admin->id, 'action' => 'unarchive', 'target_type' => 'timeline', 'target_id' => $timeline->id, ]); }); // =========================================== // Pagination Tests // =========================================== test('pagination displays correct number of items', function () { Timeline::factory()->count(20)->create(); $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->assertSet('perPage', 15); }); test('can change items per page', function () { $this->actingAs($this->admin); Volt::test('admin.timelines.index') ->set('perPage', 25) ->assertSet('perPage', 25); }); test('changing per page resets pagination', function () { Timeline::factory()->count(30)->create(); $this->actingAs($this->admin); // Navigate to page 2, then change perPage $component = Volt::test('admin.timelines.index'); // Simulate being on page 2 then changing perPage $component->set('perPage', 50); // After changing perPage, page should reset $timelines = $component->viewData('timelines'); expect($timelines->currentPage())->toBe(1); }); // =========================================== // Eager Loading Tests // =========================================== test('dashboard uses eager loading to prevent N+1 queries', function () { // Create 10 timelines with updates $timelines = Timeline::factory()->count(10)->create(); foreach ($timelines as $timeline) { TimelineUpdate::factory()->count(3)->create(['timeline_id' => $timeline->id]); } $this->actingAs($this->admin); // This test verifies the component loads without errors // The eager loading is verified by checking the query includes relationships Volt::test('admin.timelines.index') ->assertHasNoErrors(); // Verify that timelines have their relationships loaded $component = Volt::test('admin.timelines.index'); $loadedTimelines = $component->viewData('timelines'); foreach ($loadedTimelines as $timeline) { // These relationships should be already loaded (not lazy loaded) expect($timeline->relationLoaded('user'))->toBeTrue(); expect($timeline->relationLoaded('updates'))->toBeTrue(); } }); // =========================================== // Filter Reset on Search Tests // =========================================== test('updating search resets pagination', function () { Timeline::factory()->count(20)->create(); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.index'); // Change search should reset page $component->set('search', 'test'); $timelines = $component->viewData('timelines'); expect($timelines->currentPage())->toBe(1); }); test('updating status filter resets pagination', function () { Timeline::factory()->count(20)->create(); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.index'); $component->set('statusFilter', 'active'); $timelines = $component->viewData('timelines'); expect($timelines->currentPage())->toBe(1); }); test('updating client filter resets pagination', function () { $client = User::factory()->individual()->create(); Timeline::factory()->count(20)->create(['user_id' => $client->id]); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.index'); $component->set('clientFilter', $client->id); $timelines = $component->viewData('timelines'); expect($timelines->currentPage())->toBe(1); }); // =========================================== // Client List Tests // =========================================== test('client filter only shows clients with timelines', function () { $clientWithTimeline = User::factory()->individual()->create(['full_name' => 'Has Timeline']); $clientWithoutTimeline = User::factory()->individual()->create(['full_name' => 'No Timeline']); Timeline::factory()->create(['user_id' => $clientWithTimeline->id]); $this->actingAs($this->admin); $component = Volt::test('admin.timelines.index'); $clients = $component->viewData('clients'); expect($clients->pluck('id')->toArray())->toContain($clientWithTimeline->id); expect($clients->pluck('id')->toArray())->not->toContain($clientWithoutTimeline->id); });