insert([ 'id' => $id, 'type' => $data['type'], 'notifiable_type' => User::class, 'notifiable_id' => $user->getKey(), 'data' => json_encode($data, JSON_THROW_ON_ERROR), 'read_at' => $readAt, 'created_at' => now(), 'updated_at' => now(), ]); return $id; } it('lists paginated mobile notifications with the unread count', function () { $user = User::factory()->create(); $readNotificationId = storeMobileNotification($user, [ 'type' => 'new_follower', 'category' => 'social', 'title' => 'Nouvel abonné', 'body' => 'Alex vous suit maintenant.', 'url' => 'bowli://users/123', ], now()->toISOString()); $unreadNotificationId = storeMobileNotification($user, [ 'type' => 'payment_failed', 'category' => 'account', 'title' => 'Paiement échoué', 'body' => 'Vérifiez votre moyen de paiement.', 'url' => 'bowli://profile/settings/billing', ]); DB::table('notifications')->insert([ 'id' => (string) Str::uuid(), 'type' => 'filament', 'notifiable_type' => User::class, 'notifiable_id' => $user->getKey(), 'data' => json_encode(['format' => 'filament'], JSON_THROW_ON_ERROR), 'read_at' => null, 'created_at' => now(), 'updated_at' => now(), ]); Sanctum::actingAs($user); $this->getJson('/api/notifications?per_page=1') ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.id', $unreadNotificationId) ->assertJsonPath('data.0.readAt', null) ->assertJsonPath('meta.total', 2) ->assertJsonPath('unreadCount', 1); $this->getJson('/api/notifications?per_page=1&page=2') ->assertOk() ->assertJsonPath('data.0.id', $readNotificationId); }); it('marks only the authenticated users notifications as read', function () { $user = User::factory()->create(); $otherUser = User::factory()->create(); $firstId = storeMobileNotification($user, [ 'type' => 'new_follower', 'category' => 'social', 'title' => 'Nouvel abonné', 'body' => 'Alex vous suit maintenant.', 'url' => 'bowli://users/123', ]); storeMobileNotification($user, [ 'type' => 'meal_commented', 'category' => 'social', 'title' => 'Nouveau commentaire', 'body' => 'Un commentaire.', 'url' => 'bowli://meals/123', ]); $otherId = storeMobileNotification($otherUser, [ 'type' => 'payment_failed', 'category' => 'account', 'title' => 'Paiement échoué', 'body' => 'Paiement échoué.', 'url' => 'bowli://profile/settings/billing', ]); Sanctum::actingAs($user); $this->patchJson("/api/notifications/{$otherId}/read")->assertNotFound(); $this->patchJson("/api/notifications/{$firstId}/read") ->assertOk() ->assertJsonPath('data.id', $firstId) ->assertJsonPath('data.readAt', fn ($value): bool => is_string($value)); $this->getJson('/api/notifications/unread-count') ->assertOk() ->assertJsonPath('data.count', 1); $this->patchJson('/api/notifications/read-all') ->assertOk() ->assertJsonPath('data.updated', 1); $this->getJson('/api/notifications/unread-count') ->assertOk() ->assertJsonPath('data.count', 0); }); it('shows and updates notification preferences', function () { $user = User::factory()->create(); Sanctum::actingAs($user); $this->getJson('/api/notification-preferences') ->assertOk() ->assertJsonPath('data.socialNotificationsEnabled', true) ->assertJsonPath('data.engagementRemindersEnabled', true); $this->patchJson('/api/notification-preferences', [ 'socialNotificationsEnabled' => false, 'engagementRemindersEnabled' => false, ]) ->assertOk() ->assertJsonPath('data.socialNotificationsEnabled', false) ->assertJsonPath('data.engagementRemindersEnabled', false); expect($user->fresh()) ->social_notifications_enabled->toBeFalse() ->engagement_reminders_enabled->toBeFalse(); $this->patchJson('/api/notification-preferences', [ 'socialNotificationsEnabled' => 'no', ])->assertUnprocessable(); });