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');
});
});