activityData = [ [ 'id' => 1, 'description' => 'Created new workspace', 'log_name' => 'workspace', 'event' => 'created', 'causer_name' => 'John Doe', 'subject_type' => 'Workspace', 'subject_name' => 'My Project', 'changes' => null, 'timestamp' => '2026-03-20 10:00:00', ], [ 'id' => 2, 'description' => 'Updated user profile', 'log_name' => 'user', 'event' => 'updated', 'causer_name' => 'Jane Smith', 'subject_type' => 'User', 'subject_name' => 'Jane Smith', 'changes' => ['old' => ['name' => 'Jane'], 'new' => ['name' => 'Jane Smith']], 'timestamp' => '2026-03-21 14:30:00', ], [ 'id' => 3, 'description' => 'Deleted old service', 'log_name' => 'service', 'event' => 'deleted', 'causer_name' => 'Admin', 'subject_type' => 'Service', 'subject_name' => 'Legacy API', 'changes' => null, 'timestamp' => '2026-03-22 09:15:00', ], [ 'id' => 4, 'description' => 'Updated workspace settings', 'log_name' => 'workspace', 'event' => 'updated', 'causer_name' => 'John Doe', 'subject_type' => 'Workspace', 'subject_name' => 'My Project', 'changes' => ['old' => ['public' => false], 'new' => ['public' => true]], 'timestamp' => '2026-03-23 11:45:00', ], [ 'id' => 5, 'description' => 'Created new user account', 'log_name' => 'user', 'event' => 'created', 'causer_name' => 'System', 'subject_type' => 'User', 'subject_name' => 'New User', 'changes' => null, 'timestamp' => '2026-03-24 08:00:00', ], ]; } public function clearFilters(): void { $this->search = ''; $this->logName = ''; $this->event = ''; $this->page = 1; } #[Computed] public function logNames(): array { return array_values(array_unique(array_column($this->activityData, 'log_name'))); } #[Computed] public function events(): array { return array_values(array_unique(array_column($this->activityData, 'event'))); } #[Computed] public function logNameOptions(): array { $options = ['' => 'All logs']; foreach ($this->logNames as $name) { $options[$name] = ucfirst($name); } return $options; } #[Computed] public function eventOptions(): array { $options = ['' => 'All events']; foreach ($this->events as $eventName) { $options[$eventName] = ucfirst($eventName); } return $options; } #[Computed] public function filteredActivities(): array { $result = $this->activityData; if ($this->logName) { $result = array_filter($result, fn ($a) => $a['log_name'] === $this->logName); } if ($this->event) { $result = array_filter($result, fn ($a) => $a['event'] === $this->event); } if ($this->search) { $search = strtolower($this->search); $result = array_filter($result, fn ($a) => str_contains(strtolower($a['description']), $search)); } return array_values($result); } #[Computed] public function activityItems(): array { return array_map(function ($activity) { $item = [ 'description' => $activity['description'], 'event' => $activity['event'], 'timestamp' => $activity['timestamp'], ]; if ($activity['causer_name']) { $item['actor'] = [ 'name' => $activity['causer_name'], 'initials' => substr($activity['causer_name'], 0, 1), ]; } if ($activity['subject_type']) { $item['subject'] = [ 'type' => $activity['subject_type'], 'name' => $activity['subject_name'], ]; } if ($activity['changes']) { $item['changes'] = $activity['changes']; } return $item; }, $this->filteredActivities); } public function render(): string { return <<<'HTML'
Results: {{ count($this->filteredActivities) }} Log: {{ $logName ?: 'all' }} Event: {{ $event ?: 'all' }} @foreach($this->activityItems as $item)
{{ $item['description'] }}
@endforeach
HTML; } } // ============================================================================= // Initial State Tests // ============================================================================= describe('ActivityLog initial state', function () { it('starts with empty filters', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet('search', '') ->assertSet('logName', '') ->assertSet('event', ''); }); it('loads activity data on mount', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet('activityData', fn ($data) => count($data) === 5); }); it('shows all activities initially', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSee('Results: 5'); }); }); // ============================================================================= // Search Filter Tests // ============================================================================= describe('ActivityLog search filtering', function () { it('filters by search term in description', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'workspace') ->assertSee('Results: 2') ->assertSee('Created new workspace') ->assertSee('Updated workspace settings'); }); it('search is case-insensitive', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'WORKSPACE') ->assertSee('Results: 2'); }); it('returns no results for non-matching search', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'nonexistent-term') ->assertSee('Results: 0'); }); it('shows all results when search is cleared', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'workspace') ->assertSee('Results: 2') ->set('search', '') ->assertSee('Results: 5'); }); it('filters by partial description match', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'user') ->assertSee('Results: 2'); }); }); // ============================================================================= // Log Name Filter Tests // ============================================================================= describe('ActivityLog log name filtering', function () { it('filters by workspace log', function () { Livewire::test(ActivityLogModalDouble::class) ->set('logName', 'workspace') ->assertSee('Results: 2') ->assertSee('Log: workspace'); }); it('filters by user log', function () { Livewire::test(ActivityLogModalDouble::class) ->set('logName', 'user') ->assertSee('Results: 2'); }); it('filters by service log', function () { Livewire::test(ActivityLogModalDouble::class) ->set('logName', 'service') ->assertSee('Results: 1'); }); it('shows all logs when filter is empty', function () { Livewire::test(ActivityLogModalDouble::class) ->set('logName', '') ->assertSee('Results: 5') ->assertSee('Log: all'); }); it('provides log name options with All logs default', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => $component->logNameOptions[''] === 'All logs' && array_key_exists('workspace', $component->logNameOptions) && array_key_exists('user', $component->logNameOptions) && array_key_exists('service', $component->logNameOptions)); }); it('returns distinct log names', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => count($component->logNames) === 3); }); }); // ============================================================================= // Event Filter Tests // ============================================================================= describe('ActivityLog event filtering', function () { it('filters by created events', function () { Livewire::test(ActivityLogModalDouble::class) ->set('event', 'created') ->assertSee('Results: 2') ->assertSee('Event: created'); }); it('filters by updated events', function () { Livewire::test(ActivityLogModalDouble::class) ->set('event', 'updated') ->assertSee('Results: 2'); }); it('filters by deleted events', function () { Livewire::test(ActivityLogModalDouble::class) ->set('event', 'deleted') ->assertSee('Results: 1'); }); it('shows all events when filter is empty', function () { Livewire::test(ActivityLogModalDouble::class) ->set('event', '') ->assertSee('Results: 5') ->assertSee('Event: all'); }); it('provides event options with All events default', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => $component->eventOptions[''] === 'All events' && array_key_exists('created', $component->eventOptions) && array_key_exists('updated', $component->eventOptions) && array_key_exists('deleted', $component->eventOptions)); }); it('returns distinct event types', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => count($component->events) === 3); }); }); // ============================================================================= // Combined Filter Tests // ============================================================================= describe('ActivityLog combined filters', function () { it('combines log name and event filters', function () { Livewire::test(ActivityLogModalDouble::class) ->set('logName', 'workspace') ->set('event', 'created') ->assertSee('Results: 1') ->assertSee('Created new workspace'); }); it('combines search with log name filter', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'settings') ->set('logName', 'workspace') ->assertSee('Results: 1') ->assertSee('Updated workspace settings'); }); it('combines all three filters', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'workspace') ->set('logName', 'workspace') ->set('event', 'updated') ->assertSee('Results: 1'); }); it('returns empty when combined filters exclude everything', function () { Livewire::test(ActivityLogModalDouble::class) ->set('logName', 'service') ->set('event', 'created') ->assertSee('Results: 0'); }); }); // ============================================================================= // Clear Filters Tests // ============================================================================= describe('ActivityLog clear filters', function () { it('clears all filters at once', function () { Livewire::test(ActivityLogModalDouble::class) ->set('search', 'workspace') ->set('logName', 'workspace') ->set('event', 'created') ->call('clearFilters') ->assertSet('search', '') ->assertSet('logName', '') ->assertSet('event', '') ->assertSee('Results: 5'); }); it('resets page number when clearing filters', function () { Livewire::test(ActivityLogModalDouble::class) ->set('page', 3) ->call('clearFilters') ->assertSet('page', 1); }); }); // ============================================================================= // Activity Item Mapping Tests // ============================================================================= describe('ActivityLog item mapping', function () { it('maps activity items with actor information', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => $component->activityItems[0]['actor']['name'] === 'John Doe' && $component->activityItems[0]['actor']['initials'] === 'J'); }); it('maps activity items with subject information', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => $component->activityItems[0]['subject']['type'] === 'Workspace' && $component->activityItems[0]['subject']['name'] === 'My Project'); }); it('includes changes diff when present', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => isset($component->activityItems[1]['changes']) && $component->activityItems[1]['changes']['old']['name'] === 'Jane' && $component->activityItems[1]['changes']['new']['name'] === 'Jane Smith'); }); it('omits changes when not present', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => ! isset($component->activityItems[0]['changes'])); }); it('includes event type in each item', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => $component->activityItems[0]['event'] === 'created' && $component->activityItems[1]['event'] === 'updated' && $component->activityItems[2]['event'] === 'deleted'); }); it('includes timestamp in each item', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSet(fn ($component) => $component->activityItems[0]['timestamp'] === '2026-03-20 10:00:00'); }); it('renders activity descriptions in view', function () { Livewire::test(ActivityLogModalDouble::class) ->assertSee('Created new workspace') ->assertSee('Updated user profile') ->assertSee('Deleted old service'); }); });