437 lines
13 KiB
PHP
437 lines
13 KiB
PHP
<?php
|
|
|
|
use App\Enums\TimelineStatus;
|
|
use App\Models\Timeline;
|
|
use App\Models\TimelineUpdate;
|
|
use App\Models\User;
|
|
use Livewire\Volt\Volt;
|
|
|
|
beforeEach(function () {
|
|
$this->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);
|
|
});
|