create(); $artwork = Artwork::factory()->create(); ActivityEvent::record( actorId: $user->id, type: ActivityEvent::TYPE_FAVORITE, targetType: ActivityEvent::TARGET_ARTWORK, targetId: $artwork->id, meta: ['source' => 'test'], ); $this->assertDatabaseHas('activity_events', [ 'actor_id' => $user->id, 'type' => 'favorite', 'target_type' => 'artwork', 'target_id' => $artwork->id, ]); }); it('stores all five event types without error', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create(); $events = [ [ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id], [ActivityEvent::TYPE_COMMENT, ActivityEvent::TARGET_ARTWORK, $artwork->id], [ActivityEvent::TYPE_FAVORITE, ActivityEvent::TARGET_ARTWORK, $artwork->id], [ActivityEvent::TYPE_AWARD, ActivityEvent::TARGET_ARTWORK, $artwork->id], [ActivityEvent::TYPE_FOLLOW, ActivityEvent::TARGET_USER, $user->id], ]; foreach ($events as [$type, $targetType, $targetId]) { ActivityEvent::record($user->id, $type, $targetType, $targetId); } expect(ActivityEvent::where('actor_id', $user->id)->count())->toBe(5); }); it('created_at is populated on the returned instance', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create(); $event = ActivityEvent::record( $user->id, ActivityEvent::TYPE_COMMENT, ActivityEvent::TARGET_ARTWORK, $artwork->id, ); expect($event->created_at)->not->toBeNull(); }); it('actor relation resolves after record()', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create(); $event = ActivityEvent::record($user->id, ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id); expect($event->actor->id)->toBe($user->id); }); it('meta is null when empty array is passed', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create(); $event = ActivityEvent::record($user->id, ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id); expect($event->meta)->toBeNull(); }); it('meta is stored when non-empty array is passed', function () { $user = User::factory()->create(); $artwork = Artwork::factory()->create(); $event = ActivityEvent::record( $user->id, ActivityEvent::TYPE_AWARD, ActivityEvent::TARGET_ARTWORK, $artwork->id, ['medal' => 'gold'], ); expect($event->meta)->toBe(['medal' => 'gold']); }); // ── Community activity feed route ───────────────────────────────────────────── it('global activity feed returns 200 for guests', function () { $this->get('/community/activity')->assertStatus(200); }); it('following tab returns 200 for users with no follows', function () { $user = User::factory()->create(); $this->actingAs($user) ->get('/community/activity?filter=following') ->assertStatus(200); }); it('following tab shows only events from followed users', function () { $user = User::factory()->create(); $creator = User::factory()->create(); $other = User::factory()->create(); $artwork = Artwork::factory()->create(); // user_followers has no updated_at column DB::table('user_followers')->insert([ 'user_id' => $creator->id, 'follower_id' => $user->id, 'created_at' => now(), ]); // Event from followed creator ActivityEvent::record($creator->id, ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id); // Event from non-followed user (should not appear) ActivityEvent::record($other->id, ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id); $response = $this->actingAs($user)->get('/community/activity?filter=following'); $response->assertStatus(200); $props = $response->viewData('props'); $events = collect($props['initialActivities'] ?? []); expect($events)->toHaveCount(1); expect(data_get($events->first(), 'user.id'))->toBe($creator->id); });