diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/FeedsClient.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/FeedsClient.kt index 92c2e428c..39a6b22e6 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/FeedsClient.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/FeedsClient.kt @@ -25,6 +25,7 @@ import io.getstream.feeds.android.client.api.file.FeedUploader import io.getstream.feeds.android.client.api.model.ActivityData import io.getstream.feeds.android.client.api.model.AppData import io.getstream.feeds.android.client.api.model.BatchFollowData +import io.getstream.feeds.android.client.api.model.BookmarkFolderData import io.getstream.feeds.android.client.api.model.CollectionData import io.getstream.feeds.android.client.api.model.FeedId import io.getstream.feeds.android.client.api.model.FeedsConfig @@ -77,6 +78,7 @@ import io.getstream.feeds.android.network.models.FollowBatchRequest import io.getstream.feeds.android.network.models.GetOGResponse import io.getstream.feeds.android.network.models.ListDevicesResponse import io.getstream.feeds.android.network.models.UnfollowBatchRequest +import io.getstream.feeds.android.network.models.UpdateBookmarkFolderRequest import io.getstream.feeds.android.network.models.UpdateCollectionsRequest import io.getstream.feeds.android.network.models.UpsertPushPreferencesRequest import io.getstream.feeds.android.network.models.UpsertPushPreferencesResponse @@ -318,6 +320,27 @@ public interface FeedsClient { */ public fun bookmarkFolderList(query: BookmarkFoldersQuery): BookmarkFolderList + /** + * Updates a bookmark folder by its ID. + * + * @param folderId The unique identifier of the bookmark folder to update. + * @param request The request containing the updated folder data. + * @return A [Result] containing the updated [BookmarkFolderData] if successful, or an error if + * the operation fails. + */ + public suspend fun updateBookmarkFolder( + folderId: String, + request: UpdateBookmarkFolderRequest, + ): Result + + /** + * Deletes a bookmark folder by its ID. All bookmarks in the folder will also be deleted. + * + * @param folderId The unique identifier of the bookmark folder to delete. + * @return A [Result] indicating success or failure of the deletion operation. + */ + public suspend fun deleteBookmarkFolder(folderId: String): Result + /** * Creates a comment list instance based on the provided query. * diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/state/Feed.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/state/Feed.kt index f98faf338..f24041dc5 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/state/Feed.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/state/Feed.kt @@ -41,6 +41,7 @@ import io.getstream.feeds.android.network.models.UpdateBookmarkRequest import io.getstream.feeds.android.network.models.UpdateCommentRequest import io.getstream.feeds.android.network.models.UpdateFeedMembersRequest import io.getstream.feeds.android.network.models.UpdateFeedRequest +import io.getstream.feeds.android.network.models.UpdateFollowRequest /** * A feed represents a collection of activities and provides methods to interact with them. @@ -166,6 +167,18 @@ public interface Feed { deleteNotificationActivity: Boolean? = null, ): Result + /** + * Restores a soft-deleted activity. + * + * Only the activity owner can restore their own activities (for client-side requests). + * Hard-deleted activities cannot be restored. + * + * @param id The unique identifier of the activity to restore. + * @return A [Result] containing the restored [ActivityData] if successful, or an error if the + * operation fails. + */ + public suspend fun restoreActivity(id: String): Result + /** * Marks an activity as read or unread. * @@ -328,6 +341,21 @@ public interface Feed { pushPreference: FollowRequest.PushPreference? = null, ): Result + /** + * Updates a follow relationship with new custom data or push preferences. + * + * @param targetFid The target feed identifier of the follow to update. + * @param custom Additional data for the follow relationship. + * @param pushPreference Push notification preferences for the follow. + * @return A [Result] containing the updated [FollowData] if successful, or an error if the + * operation fails. + */ + public suspend fun updateFollow( + targetFid: FeedId, + custom: Map? = null, + pushPreference: UpdateFollowRequest.PushPreference? = null, + ): Result + /** * Unfollows another feed. * diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt index 64ea1bf1b..f70f3fc8b 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/client/FeedsClientImpl.kt @@ -29,6 +29,7 @@ import io.getstream.feeds.android.client.api.Moderation import io.getstream.feeds.android.client.api.file.FeedUploader import io.getstream.feeds.android.client.api.model.ActivityData import io.getstream.feeds.android.client.api.model.BatchFollowData +import io.getstream.feeds.android.client.api.model.BookmarkFolderData import io.getstream.feeds.android.client.api.model.FeedId import io.getstream.feeds.android.client.api.model.FollowData import io.getstream.feeds.android.client.api.model.ModelUpdates @@ -96,6 +97,7 @@ import io.getstream.feeds.android.client.internal.state.PollListImpl import io.getstream.feeds.android.client.internal.state.PollVoteListImpl import io.getstream.feeds.android.client.internal.state.UserListImpl import io.getstream.feeds.android.client.internal.state.event.StateEventEnricher +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.FollowBatchUpdate import io.getstream.feeds.android.client.internal.state.event.handler.OnNewActivity import io.getstream.feeds.android.client.internal.state.event.toModel @@ -109,6 +111,7 @@ import io.getstream.feeds.android.network.models.DeleteActivitiesRequest import io.getstream.feeds.android.network.models.DeleteActivitiesResponse import io.getstream.feeds.android.network.models.FollowBatchRequest import io.getstream.feeds.android.network.models.UnfollowBatchRequest +import io.getstream.feeds.android.network.models.UpdateBookmarkFolderRequest import io.getstream.feeds.android.network.models.WSEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow @@ -308,6 +311,21 @@ internal class FeedsClientImpl( subscriptionManager = stateEventsSubscriptionManager, ) + override suspend fun updateBookmarkFolder( + folderId: String, + request: UpdateBookmarkFolderRequest, + ): Result { + return bookmarksRepository.updateBookmarkFolder(folderId, request).onSuccess { + stateEventsSubscriptionManager.onEvent(StateUpdateEvent.BookmarkFolderUpdated(it)) + } + } + + override suspend fun deleteBookmarkFolder(folderId: String): Result { + return bookmarksRepository.deleteBookmarkFolder(folderId).onSuccess { + stateEventsSubscriptionManager.onEvent(StateUpdateEvent.BookmarkFolderDeleted(folderId)) + } + } + override fun commentList(query: CommentsQuery): CommentList = CommentListImpl( query = query, diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepository.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepository.kt index cec0cf313..ddc81936d 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepository.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepository.kt @@ -82,6 +82,14 @@ internal interface ActivitiesRepository { */ suspend fun deleteActivities(request: DeleteActivitiesRequest): Result + /** + * Restores a soft-deleted activity. + * + * @param activityId The ID of the activity to restore. + * @return A [Result] containing the restored [ActivityData] or an error. + */ + suspend fun restoreActivity(activityId: String): Result + /** * Retrieves an activity by its ID. * diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImpl.kt index edff5af1c..c1411ab67 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImpl.kt @@ -92,6 +92,10 @@ internal class ActivitiesRepositoryImpl( request: DeleteActivitiesRequest ): Result = runSafely { api.deleteActivities(request) } + override suspend fun restoreActivity(activityId: String): Result = runSafely { + api.restoreActivity(id = activityId).activity.toModel() + } + override suspend fun getActivity(activityId: String): Result = runSafely { api.getActivity(activityId).activity.toModel() } diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepository.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepository.kt index 01a0e6e44..a70055e44 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepository.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepository.kt @@ -22,6 +22,7 @@ import io.getstream.feeds.android.client.api.state.query.BookmarkFoldersQuery import io.getstream.feeds.android.client.api.state.query.BookmarksQuery import io.getstream.feeds.android.client.internal.model.PaginationResult import io.getstream.feeds.android.network.models.AddBookmarkRequest +import io.getstream.feeds.android.network.models.UpdateBookmarkFolderRequest import io.getstream.feeds.android.network.models.UpdateBookmarkRequest internal interface BookmarksRepository { @@ -40,4 +41,11 @@ internal interface BookmarksRepository { suspend fun queryBookmarkFolders( query: BookmarkFoldersQuery ): Result> + + suspend fun updateBookmarkFolder( + folderId: String, + request: UpdateBookmarkFolderRequest, + ): Result + + suspend fun deleteBookmarkFolder(folderId: String): Result } diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImpl.kt index 5a1d61704..631739dab 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImpl.kt @@ -27,6 +27,7 @@ import io.getstream.feeds.android.client.internal.model.toModel import io.getstream.feeds.android.client.internal.state.query.toRequest import io.getstream.feeds.android.network.apis.FeedsApi import io.getstream.feeds.android.network.models.AddBookmarkRequest +import io.getstream.feeds.android.network.models.UpdateBookmarkFolderRequest import io.getstream.feeds.android.network.models.UpdateBookmarkRequest /** @@ -76,4 +77,15 @@ internal class BookmarksRepositoryImpl(private val api: FeedsApi) : BookmarksRep pagination = PaginationData(response.next, response.prev), ) } + + override suspend fun updateBookmarkFolder( + folderId: String, + request: UpdateBookmarkFolderRequest, + ): Result = runSafely { + api.updateBookmarkFolder(folderId, request).bookmarkFolder.toModel() + } + + override suspend fun deleteBookmarkFolder(folderId: String): Result = runSafely { + api.deleteBookmarkFolder(folderId) + } } diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepository.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepository.kt index 36bb68c18..29a14545a 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepository.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepository.kt @@ -40,6 +40,7 @@ import io.getstream.feeds.android.network.models.RejectFollowRequest import io.getstream.feeds.android.network.models.UnfollowBatchRequest import io.getstream.feeds.android.network.models.UpdateFeedMembersRequest import io.getstream.feeds.android.network.models.UpdateFeedRequest +import io.getstream.feeds.android.network.models.UpdateFollowRequest /** * Represents the repository for managing feeds. Performs requests and transforms API models to @@ -98,6 +99,8 @@ internal interface FeedsRepository { suspend fun rejectFollow(request: RejectFollowRequest): Result + suspend fun updateFollow(request: UpdateFollowRequest): Result + // END: Follows // BEGIN: Members diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImpl.kt index 48ba0975b..ddc159cde 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImpl.kt @@ -44,6 +44,7 @@ import io.getstream.feeds.android.network.models.RejectFollowRequest import io.getstream.feeds.android.network.models.UnfollowBatchRequest import io.getstream.feeds.android.network.models.UpdateFeedMembersRequest import io.getstream.feeds.android.network.models.UpdateFeedRequest +import io.getstream.feeds.android.network.models.UpdateFollowRequest /** * Default implementation of the [FeedsRepository] interface. @@ -175,6 +176,11 @@ internal class FeedsRepositoryImpl(private val api: FeedsApi) : FeedsRepository api.rejectFollow(request).follow.toModel() } + override suspend fun updateFollow(request: UpdateFollowRequest): Result = + runSafely { + api.updateFollow(request).follow.toModel() + } + override suspend fun updateFeedMembers( feedGroupId: String, feedId: String, diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedImpl.kt index c23f7a9ef..bd40a1cfb 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedImpl.kt @@ -65,6 +65,7 @@ import io.getstream.feeds.android.network.models.UpdateBookmarkRequest import io.getstream.feeds.android.network.models.UpdateCommentRequest import io.getstream.feeds.android.network.models.UpdateFeedMembersRequest import io.getstream.feeds.android.network.models.UpdateFeedRequest +import io.getstream.feeds.android.network.models.UpdateFollowRequest /** * A feed represents a collection of activities and provides methods to interact with them. @@ -218,6 +219,10 @@ internal class FeedImpl( } } + override suspend fun restoreActivity(id: String): Result { + return activitiesRepository.restoreActivity(id) + } + override suspend fun markActivity(request: MarkActivityRequest): Result { return activitiesRepository.markActivity( feedGroupId = group, @@ -369,6 +374,23 @@ internal class FeedImpl( } } + override suspend fun updateFollow( + targetFid: FeedId, + custom: Map?, + pushPreference: UpdateFollowRequest.PushPreference?, + ): Result { + val request = + UpdateFollowRequest( + source = fid.rawValue, + target = targetFid.rawValue, + custom = custom, + pushPreference = pushPreference, + ) + return feedsRepository.updateFollow(request).onSuccess { + subscriptionManager.onEvent(StateUpdateEvent.FollowUpdated(it)) + } + } + override suspend fun unfollow( targetFid: FeedId, deleteNotificationActivity: Boolean?, diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImplTest.kt index ad485bd46..1103fd787 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/ActivitiesRepositoryImplTest.kt @@ -49,6 +49,7 @@ import io.getstream.feeds.android.network.models.MarkActivityRequest import io.getstream.feeds.android.network.models.QueryActivitiesResponse import io.getstream.feeds.android.network.models.QueryActivityReactionsRequest import io.getstream.feeds.android.network.models.QueryActivityReactionsResponse +import io.getstream.feeds.android.network.models.RestoreActivityResponse import io.getstream.feeds.android.network.models.UpdateActivityPartialRequest import io.getstream.feeds.android.network.models.UpdateActivityPartialResponse import io.getstream.feeds.android.network.models.UpdateActivityRequest @@ -336,4 +337,16 @@ internal class ActivitiesRepositoryImplTest { repositoryResult = Unit, ) } + + @Test + fun `on restoreActivity, delegate to api`() { + val apiResult = RestoreActivityResponse("duration", activityResponse()) + + testDelegation( + apiFunction = { feedsApi.restoreActivity("activityId") }, + repositoryCall = { repository.restoreActivity("activityId") }, + apiResult = apiResult, + repositoryResult = apiResult.activity.toModel(), + ) + } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImplTest.kt index cebc08148..72595fa1b 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/BookmarksRepositoryImplTest.kt @@ -31,6 +31,8 @@ import io.getstream.feeds.android.network.models.AddBookmarkResponse import io.getstream.feeds.android.network.models.DeleteBookmarkResponse import io.getstream.feeds.android.network.models.QueryBookmarkFoldersResponse import io.getstream.feeds.android.network.models.QueryBookmarksResponse +import io.getstream.feeds.android.network.models.UpdateBookmarkFolderRequest +import io.getstream.feeds.android.network.models.UpdateBookmarkFolderResponse import io.getstream.feeds.android.network.models.UpdateBookmarkRequest import io.getstream.feeds.android.network.models.UpdateBookmarkResponse import io.mockk.mockk @@ -140,4 +142,27 @@ internal class BookmarksRepositoryImplTest { ), ) } + + @Test + fun `on updateBookmarkFolder, delegate to api`() = runTest { + val request = UpdateBookmarkFolderRequest() + val apiResult = UpdateBookmarkFolderResponse("duration", bookmarkFolderResponse()) + + testDelegation( + apiFunction = { feedsApi.updateBookmarkFolder("folder-1", request) }, + repositoryCall = { repository.updateBookmarkFolder("folder-1", request) }, + apiResult = apiResult, + repositoryResult = apiResult.bookmarkFolder.toModel(), + ) + } + + @Test + fun `on deleteBookmarkFolder, delegate to api`() = runTest { + testDelegation( + apiFunction = { feedsApi.deleteBookmarkFolder("folder-1") }, + repositoryCall = { repository.deleteBookmarkFolder("folder-1") }, + apiResult = Unit, + repositoryResult = Unit, + ) + } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImplTest.kt index 324f20ac2..c581a7fb4 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/repository/FeedsRepositoryImplTest.kt @@ -58,6 +58,8 @@ import io.getstream.feeds.android.network.models.UnfollowPair import io.getstream.feeds.android.network.models.UnfollowResponse import io.getstream.feeds.android.network.models.UpdateFeedMembersRequest import io.getstream.feeds.android.network.models.UpdateFeedRequest +import io.getstream.feeds.android.network.models.UpdateFollowRequest +import io.getstream.feeds.android.network.models.UpdateFollowResponse import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Test @@ -235,6 +237,24 @@ internal class FeedsRepositoryImplTest { ) } + @Test + fun `on updateFollow, delegate to api`() = runTest { + val request = + UpdateFollowRequest( + source = "source", + target = "target", + custom = mapOf("key" to "value"), + ) + val apiResult = UpdateFollowResponse("duration", followResponse()) + + testDelegation( + apiFunction = { feedsApi.updateFollow(request) }, + repositoryCall = { repository.updateFollow(request) }, + apiResult = apiResult, + repositoryResult = apiResult.follow.toModel(), + ) + } + @Test fun `on updateFeedMembers, delegate to api`() = runTest { val request = UpdateFeedMembersRequest(UpdateFeedMembersRequest.Operation.Remove) diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedImplTest.kt index 39b15282a..5ebf99c98 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedImplTest.kt @@ -70,6 +70,7 @@ import io.getstream.feeds.android.network.models.UpdateBookmarkRequest import io.getstream.feeds.android.network.models.UpdateCommentRequest import io.getstream.feeds.android.network.models.UpdateFeedMembersRequest import io.getstream.feeds.android.network.models.UpdateFeedRequest +import io.getstream.feeds.android.network.models.UpdateFollowRequest import io.mockk.called import io.mockk.coEvery import io.mockk.coVerify @@ -281,6 +282,20 @@ internal class FeedImplTest { verify { stateEventListener.onEvent(ActivityDeleted(FidScope.unknown, activityId)) } } + @Test + fun `on restoreActivity, delegate to repository`() = runTest { + val feed = createFeed() + val activityId = "activity-1" + val restoredActivity = activityData(activityId) + + coEvery { activitiesRepository.restoreActivity(activityId) } returns + Result.success(restoredActivity) + + val result = feed.restoreActivity(activityId) + + assertEquals(restoredActivity, result.getOrNull()) + } + @Test fun `on repost, delegate to repository and fire event`() = runTest { val feed = createFeed() @@ -401,6 +416,25 @@ internal class FeedImplTest { assertEquals(listOf(follow), feed.state.followers.value) } + @Test + fun `on updateFollow, delegate to repository and fire event`() = runTest { + val feed = createFeed() + val targetFid = FeedId("user:target") + val custom = mapOf("key" to "value") + val pushPreference = UpdateFollowRequest.PushPreference.All + val follow = followData(sourceFid = "group:id", targetFid = "user:target") + + // Set up initial state with existing follow + setupInitialState(feed, following = listOf(follow)) + + coEvery { feedsRepository.updateFollow(any()) } returns Result.success(follow) + + val result = feed.updateFollow(targetFid, custom, pushPreference) + + assertEquals(follow, result.getOrNull()) + verify { stateEventListener.onEvent(StateUpdateEvent.FollowUpdated(follow)) } + } + @Test fun `on rejectFollow, delegate to repository and update state`() = runTest { val feed = createFeed()