diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/model/ActivityData.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/model/ActivityData.kt index f3778be72..260556c73 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/model/ActivityData.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/api/model/ActivityData.kt @@ -17,6 +17,7 @@ package io.getstream.feeds.android.client.api.model import io.getstream.feeds.android.client.internal.model.addReaction import io.getstream.feeds.android.client.internal.model.removeReaction +import io.getstream.feeds.android.client.internal.utils.updateIf import io.getstream.feeds.android.client.internal.utils.upsert import io.getstream.feeds.android.network.models.ActivityLocation import io.getstream.feeds.android.network.models.ActivityResponse @@ -199,9 +200,14 @@ internal fun ActivityResponse.Visibility.toModel(): ActivityDataVisibility = /** * Extension function to update the activity while preserving own bookmarks, reactions, and poll - * votes because "own" data from WS events is not reliable. + * votes because "own" data from WS events is not reliable. Optionally, different instances can be + * provided to be used instead of the current ones. */ -internal fun ActivityData.update(updated: ActivityData): ActivityData = +internal fun ActivityData.update( + updated: ActivityData, + ownBookmarks: List = this.ownBookmarks, + ownReactions: List = this.ownReactions, +): ActivityData = updated.copy( ownBookmarks = ownBookmarks, ownReactions = ownReactions, @@ -236,46 +242,64 @@ internal fun ActivityData.removeComment(comment: CommentData): ActivityData { return this.copy(comments = updatedComments, commentCount = max(0, this.commentCount - 1)) } +internal fun List.deleteBookmark( + bookmark: BookmarkData, + currentUserId: String, +): List = + updateIf({ it.id == bookmark.activity.id }) { activity -> + activity.deleteBookmark(bookmark, currentUserId) + } + +internal fun List.upsertBookmark( + bookmark: BookmarkData, + currentUserId: String, +): List = + updateIf({ it.id == bookmark.activity.id }) { activity -> + activity.upsertBookmark(bookmark, currentUserId) + } + /** - * Adds a bookmark to the activity, updating the own bookmarks and bookmark count. + * Calls [changeBookmarks] with a [filter] operation to remove the bookmark. * - * @param bookmark The bookmark to be added. - * @param currentUserId The ID of the current user, used to determine if the bookmark belongs to - * them. - * @return A new [ActivityData] instance with the updated own bookmarks and bookmark count. + * @see changeBookmarks */ -internal fun ActivityData.addBookmark(bookmark: BookmarkData, currentUserId: String): ActivityData { - val updatedOwnBookmarks = - if (bookmark.user.id == currentUserId) { - this.ownBookmarks.upsert(bookmark, BookmarkData::id) - } else { - this.ownBookmarks - } - return this.copy(ownBookmarks = updatedOwnBookmarks, bookmarkCount = this.bookmarkCount + 1) -} +internal fun ActivityData.deleteBookmark(bookmark: BookmarkData, currentUserId: String) = + changeBookmarks(bookmark, currentUserId) { filter { it.id != bookmark.id } } + +/** + * Calls [changeBookmarks] with an [upsert] operation. + * + * @see changeBookmarks + */ +internal fun ActivityData.upsertBookmark( + bookmark: BookmarkData, + currentUserId: String, +): ActivityData = changeBookmarks(bookmark, currentUserId) { upsert(bookmark, BookmarkData::id) } /** - * Deletes a bookmark from the activity, updating the own bookmarks and bookmark count. + * Merges the receiver activity with [bookmark]'s [BookmarkData.activity] and updates own bookmarks + * using the provided [updateOwnBookmarks] function if the bookmark belongs to the current user. * - * @param bookmark The bookmark to be deleted. + * @param bookmark The bookmark that was added or removed. * @param currentUserId The ID of the current user, used to determine if the bookmark belongs to * them. - * @return A new [ActivityData] instance with the updated own bookmarks and bookmark count. + * @param updateOwnBookmarks A function that takes the current list of own bookmarks and returns the + * updated list of own bookmarks. + * @return The updated [ActivityData] instance. */ -internal fun ActivityData.deleteBookmark( +internal inline fun ActivityData.changeBookmarks( bookmark: BookmarkData, currentUserId: String, + updateOwnBookmarks: List.() -> List, ): ActivityData { val updatedOwnBookmarks = if (bookmark.user.id == currentUserId) { - this.ownBookmarks.filter { it.id != bookmark.id } + this.ownBookmarks.updateOwnBookmarks() } else { this.ownBookmarks } - return this.copy( - ownBookmarks = updatedOwnBookmarks, - bookmarkCount = max(0, this.bookmarkCount - 1), - ) + + return update(updated = bookmark.activity, ownBookmarks = updatedOwnBookmarks) } /** diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImpl.kt index 7a2fea766..105f10bc7 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImpl.kt @@ -21,12 +21,12 @@ import io.getstream.feeds.android.client.api.model.CommentData import io.getstream.feeds.android.client.api.model.FeedsReactionData import io.getstream.feeds.android.client.api.model.PaginationData import io.getstream.feeds.android.client.api.model.PaginationResult -import io.getstream.feeds.android.client.api.model.addBookmark import io.getstream.feeds.android.client.api.model.addComment import io.getstream.feeds.android.client.api.model.addReaction import io.getstream.feeds.android.client.api.model.deleteBookmark import io.getstream.feeds.android.client.api.model.removeComment import io.getstream.feeds.android.client.api.model.removeReaction +import io.getstream.feeds.android.client.api.model.upsertBookmark import io.getstream.feeds.android.client.api.state.ActivityListState import io.getstream.feeds.android.client.api.state.query.ActivitiesQuery import io.getstream.feeds.android.client.api.state.query.ActivitiesQueryConfig @@ -92,30 +92,12 @@ internal class ActivityListStateImpl( } } - override fun onBookmarkAdded(bookmark: BookmarkData) { - _activities.update { current -> - current.map { activity -> - if (activity.id == bookmark.activity.id) { - // If the activity matches the bookmark, add the bookmark to it - activity.addBookmark(bookmark, currentUserId) - } else { - activity - } - } - } + override fun onBookmarkRemoved(bookmark: BookmarkData) { + _activities.update { current -> current.deleteBookmark(bookmark, currentUserId) } } - override fun onBookmarkRemoved(bookmark: BookmarkData) { - _activities.update { current -> - current.map { activity -> - if (activity.id == bookmark.activity.id) { - // If the activity matches the bookmark, remove the bookmark from it - activity.deleteBookmark(bookmark, currentUserId) - } else { - activity - } - } - } + override fun onBookmarkUpserted(bookmark: BookmarkData) { + _activities.update { current -> current.upsertBookmark(bookmark, currentUserId) } } override fun onCommentAdded(comment: CommentData) { @@ -206,18 +188,18 @@ internal interface ActivityListStateUpdates { fun onActivityUpdated(activity: ActivityData) /** - * Called when a bookmark was added. + * Called when a bookmark was removed. * - * @param bookmark The bookmark that was added. + * @param bookmark The bookmark that was removed. */ - fun onBookmarkAdded(bookmark: BookmarkData) + fun onBookmarkRemoved(bookmark: BookmarkData) /** - * Called when a bookmark was removed. + * Called when a bookmark was added or updated. * - * @param bookmark The bookmark that was removed. + * @param bookmark The bookmark that was added or updated. */ - fun onBookmarkRemoved(bookmark: BookmarkData) + fun onBookmarkUpserted(bookmark: BookmarkData) /** * Called when a comment is added to an activity. diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImpl.kt index fc0c5aed8..4474ca90d 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImpl.kt @@ -21,7 +21,6 @@ import io.getstream.feeds.android.client.api.model.FeedsReactionData import io.getstream.feeds.android.client.api.model.PollData import io.getstream.feeds.android.client.api.model.PollVoteData import io.getstream.feeds.android.client.api.model.ThreadedCommentData -import io.getstream.feeds.android.client.api.model.addBookmark import io.getstream.feeds.android.client.api.model.addReaction import io.getstream.feeds.android.client.api.model.castVote import io.getstream.feeds.android.client.api.model.deleteBookmark @@ -29,6 +28,7 @@ import io.getstream.feeds.android.client.api.model.removeReaction import io.getstream.feeds.android.client.api.model.removeVote import io.getstream.feeds.android.client.api.model.setClosed import io.getstream.feeds.android.client.api.model.update +import io.getstream.feeds.android.client.api.model.upsertBookmark import io.getstream.feeds.android.client.api.state.ActivityCommentListState import io.getstream.feeds.android.client.api.state.ActivityState import kotlinx.coroutines.flow.MutableStateFlow @@ -82,14 +82,14 @@ internal class ActivityStateImpl( _activity.update { current -> current?.removeReaction(reaction, currentUserId) } } - override fun onBookmarkAdded(bookmark: BookmarkData) { - _activity.update { current -> current?.addBookmark(bookmark, currentUserId) } - } - override fun onBookmarkRemoved(bookmark: BookmarkData) { _activity.update { current -> current?.deleteBookmark(bookmark, currentUserId) } } + override fun onBookmarkUpserted(bookmark: BookmarkData) { + _activity.update { current -> current?.upsertBookmark(bookmark, currentUserId) } + } + override fun onPollClosed(poll: PollData) { updatePoll(poll.id, PollData::setClosed) } @@ -162,18 +162,18 @@ internal interface ActivityStateUpdates { fun onReactionRemoved(reaction: FeedsReactionData) /** - * Called when a bookmark is added to the activity. + * Called when a bookmark is removed from the activity. * - * @param bookmark The bookmark that was added. + * @param bookmark The bookmark that was deleted. */ - fun onBookmarkAdded(bookmark: BookmarkData) + fun onBookmarkRemoved(bookmark: BookmarkData) /** - * Called when a bookmark is removed from the activity. + * Called when a bookmark is added to or updated in an activity. * - * @param bookmark The bookmark that was deleted. + * @param bookmark The bookmark that was added or updated. */ - fun onBookmarkRemoved(bookmark: BookmarkData) + fun onBookmarkUpserted(bookmark: BookmarkData) /** * Called when the associated poll is closed. diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImpl.kt index c16b9487b..25f4d392a 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImpl.kt @@ -24,6 +24,7 @@ import io.getstream.feeds.android.client.api.state.query.BookmarksQuery import io.getstream.feeds.android.client.api.state.query.BookmarksQueryConfig import io.getstream.feeds.android.client.api.state.query.BookmarksSort import io.getstream.feeds.android.client.internal.utils.mergeSorted +import io.getstream.feeds.android.client.internal.utils.upsertSorted import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -96,22 +97,15 @@ internal class BookmarkListStateImpl(override val query: BookmarksQuery) : } } - override fun onBookmarkUpdated(bookmark: BookmarkData) { - _bookmarks.update { current -> - current.map { - if (it.id == bookmark.id) { - // Update the bookmark with the new data - bookmark - } else { - it - } - } - } - } - override fun onBookmarkRemoved(bookmark: BookmarkData) { _bookmarks.update { current -> current.filter { it.id != bookmark.id } } } + + override fun onBookmarkUpserted(bookmark: BookmarkData) { + _bookmarks.update { current -> + current.upsertSorted(bookmark, BookmarkData::id, bookmarksSorting) + } + } } /** @@ -134,7 +128,7 @@ internal interface BookmarkListStateUpdates { fun onBookmarkFolderUpdated(folder: BookmarkFolderData) - fun onBookmarkUpdated(bookmark: BookmarkData) - fun onBookmarkRemoved(bookmark: BookmarkData) + + fun onBookmarkUpserted(bookmark: BookmarkData) } diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImpl.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImpl.kt index 7525c4500..42e081382 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImpl.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImpl.kt @@ -32,7 +32,6 @@ import io.getstream.feeds.android.client.api.model.PaginationResult import io.getstream.feeds.android.client.api.model.PollData import io.getstream.feeds.android.client.api.model.PollVoteData import io.getstream.feeds.android.client.api.model.QueryConfiguration -import io.getstream.feeds.android.client.api.model.addBookmark import io.getstream.feeds.android.client.api.model.addComment import io.getstream.feeds.android.client.api.model.addReaction import io.getstream.feeds.android.client.api.model.castVote @@ -42,6 +41,7 @@ import io.getstream.feeds.android.client.api.model.removeReaction import io.getstream.feeds.android.client.api.model.removeVote import io.getstream.feeds.android.client.api.model.setClosed import io.getstream.feeds.android.client.api.model.update +import io.getstream.feeds.android.client.api.model.upsertBookmark import io.getstream.feeds.android.client.api.state.FeedState import io.getstream.feeds.android.client.api.state.query.ActivitiesQueryConfig import io.getstream.feeds.android.client.api.state.query.ActivitiesSort @@ -191,28 +191,12 @@ internal class FeedStateImpl( _pinnedActivities.update { current -> current.filter { it.activity.id != activityId } } } - override fun onBookmarkAdded(bookmark: BookmarkData) { - _activities.update { current -> - current.map { - if (it.id == bookmark.activity.id) { - it.addBookmark(bookmark, currentUserId) - } else { - it - } - } - } + override fun onBookmarkRemoved(bookmark: BookmarkData) { + _activities.update { current -> current.deleteBookmark(bookmark, currentUserId) } } - override fun onBookmarkRemoved(bookmark: BookmarkData) { - _activities.update { current -> - current.map { - if (it.id == bookmark.activity.id) { - it.deleteBookmark(bookmark, currentUserId) - } else { - it - } - } - } + override fun onBookmarkUpserted(bookmark: BookmarkData) { + _activities.update { current -> current.upsertBookmark(bookmark, currentUserId) } } override fun onCommentAdded(comment: CommentData) { @@ -438,12 +422,12 @@ internal interface FeedStateUpdates { /** Handles updates to the feed state when an activity is unpinned. */ fun onActivityUnpinned(activityId: String) - /** Handles updates to the feed state when a bookmark is added or removed. */ - fun onBookmarkAdded(bookmark: BookmarkData) - /** Handles updates to the feed state when a bookmark is removed. */ fun onBookmarkRemoved(bookmark: BookmarkData) + /** Handles updates to the feed state when a bookmark is added or updated. */ + fun onBookmarkUpserted(bookmark: BookmarkData) + /** Handles updates to the feed state when a comment is added or removed. */ fun onCommentAdded(comment: CommentData) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandler.kt index b5f3412f8..7c3acc582 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandler.kt @@ -59,7 +59,7 @@ internal class ActivityEventHandler( is StateUpdateEvent.BookmarkAdded -> { val eventActivity = event.bookmark.activity if (fid.rawValue !in eventActivity.feeds || eventActivity.id != activityId) return - state.onBookmarkAdded(event.bookmark) + state.onBookmarkUpserted(event.bookmark) } is StateUpdateEvent.BookmarkDeleted -> { @@ -68,6 +68,12 @@ internal class ActivityEventHandler( state.onBookmarkRemoved(event.bookmark) } + is StateUpdateEvent.BookmarkUpdated -> { + val eventActivity = event.bookmark.activity + if (fid.rawValue !in eventActivity.feeds || eventActivity.id != activityId) return + state.onBookmarkUpserted(event.bookmark) + } + is StateUpdateEvent.PollClosed -> { if (event.fid != fid.rawValue) return state.onPollClosed(event.poll) diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandler.kt index 1ea08d7b6..e9c6f30a7 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandler.kt @@ -27,8 +27,9 @@ internal class ActivityListEventHandler(private val state: ActivityListStateUpda is StateUpdateEvent.ActivityDeleted -> state.onActivityRemoved(event.activityId) is StateUpdateEvent.ActivityReactionAdded -> state.onReactionAdded(event.reaction) is StateUpdateEvent.ActivityReactionDeleted -> state.onReactionRemoved(event.reaction) - is StateUpdateEvent.BookmarkAdded -> state.onBookmarkAdded(event.bookmark) + is StateUpdateEvent.BookmarkAdded -> state.onBookmarkUpserted(event.bookmark) is StateUpdateEvent.BookmarkDeleted -> state.onBookmarkRemoved(event.bookmark) + is StateUpdateEvent.BookmarkUpdated -> state.onBookmarkUpserted(event.bookmark) is StateUpdateEvent.CommentAdded -> state.onCommentAdded(event.comment) is StateUpdateEvent.CommentDeleted -> state.onCommentRemoved(event.comment) else -> { diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandler.kt index 9cba69c21..98e653519 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandler.kt @@ -28,8 +28,9 @@ internal class BookmarkListEventHandler(private val state: BookmarkListStateUpda state.onBookmarkFolderRemoved(event.folderId) is StateUpdateEvent.BookmarkFolderUpdated -> state.onBookmarkFolderUpdated(event.folder) - is StateUpdateEvent.BookmarkUpdated -> state.onBookmarkUpdated(event.bookmark) + is StateUpdateEvent.BookmarkAdded -> state.onBookmarkUpserted(event.bookmark) is StateUpdateEvent.BookmarkDeleted -> state.onBookmarkRemoved(event.bookmark) + is StateUpdateEvent.BookmarkUpdated -> state.onBookmarkUpserted(event.bookmark) else -> {} } } diff --git a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandler.kt b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandler.kt index a90ab0df9..d4f15f1eb 100644 --- a/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandler.kt +++ b/stream-feeds-android-client/src/main/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandler.kt @@ -89,7 +89,7 @@ internal class FeedEventHandler(private val fid: FeedId, private val state: Feed is StateUpdateEvent.BookmarkAdded -> { if (event.bookmark.activity.feeds.contains(fid.rawValue)) { - state.onBookmarkAdded(event.bookmark) + state.onBookmarkUpserted(event.bookmark) } } @@ -99,6 +99,12 @@ internal class FeedEventHandler(private val fid: FeedId, private val state: Feed } } + is StateUpdateEvent.BookmarkUpdated -> { + if (event.bookmark.activity.feeds.contains(fid.rawValue)) { + state.onBookmarkUpserted(event.bookmark) + } + } + is StateUpdateEvent.CommentAdded -> { if (event.fid == fid.rawValue) { state.onCommentAdded(event.comment) diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImplTest.kt index 5b25580d8..35a0bb6ca 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityListStateImplTest.kt @@ -78,16 +78,16 @@ internal class ActivityListStateImplTest { } @Test - fun `on bookmarkAdded, then add bookmark to activity`() = runTest { + fun `on onBookmarkUpserted, then add bookmark to activity`() = runTest { val initialActivities = listOf(activityData("activity-1"), activityData("activity-2")) val paginationResult = defaultPaginationResult(initialActivities) activityListState.onQueryMoreActivities(paginationResult, queryConfig) - val bookmark = bookmarkData("activity-1", currentUserId) - activityListState.onBookmarkAdded(bookmark) + val expected = bookmark.activity.copy(ownBookmarks = listOf(bookmark)) + + activityListState.onBookmarkUpserted(bookmark) - val activityWithBookmark = activityListState.activities.value.first() - assertEquals(1, activityWithBookmark.bookmarkCount) + assertEquals(listOf(expected, initialActivities[1]), activityListState.activities.value) } @Test @@ -97,7 +97,7 @@ internal class ActivityListStateImplTest { activityListState.onQueryMoreActivities(paginationResult, queryConfig) val bookmark = bookmarkData("activity-1", currentUserId) - activityListState.onBookmarkAdded(bookmark) + activityListState.onBookmarkUpserted(bookmark) activityListState.onBookmarkRemoved(bookmark) val activityWithoutBookmark = activityListState.activities.value.first() diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImplTest.kt index ea6c00db6..43097569b 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/ActivityStateImplTest.kt @@ -188,15 +188,14 @@ internal class ActivityStateImplTest { } @Test - fun `on onBookmarkAdded, then add bookmark to activity`() = runTest { + fun `on onBookmarkUpserted, then add bookmark to activity`() = runTest { val initialActivity = activityData("activity-1") setupInitialActivity(initialActivity) val bookmark = bookmarkData("activity-1", currentUserId) - activityState.onBookmarkAdded(bookmark) + activityState.onBookmarkUpserted(bookmark) - val expectedActivity = - initialActivity.copy(bookmarkCount = 1, ownBookmarks = listOf(bookmark)) + val expectedActivity = bookmark.activity.copy(ownBookmarks = listOf(bookmark)) assertEquals(expectedActivity, activityState.activity.value) } @@ -206,7 +205,7 @@ internal class ActivityStateImplTest { setupInitialActivity(initialActivity) val bookmark = bookmarkData("activity-1", currentUserId) - activityState.onBookmarkAdded(bookmark) + activityState.onBookmarkUpserted(bookmark) activityState.onBookmarkRemoved(bookmark) val expectedActivity = initialActivity.copy(bookmarkCount = 0, ownBookmarks = emptyList()) @@ -233,16 +232,17 @@ internal class ActivityStateImplTest { } @Test - fun `on onBookmarkAdded from other user, then update count but not ownBookmarks`() = runTest { - val initialActivity = activityData("activity-1") - setupInitialActivity(initialActivity) + fun `on onBookmarkUpserted from other user, then update count but not ownBookmarks`() = + runTest { + val initialActivity = activityData("activity-1") + setupInitialActivity(initialActivity) - val bookmark = bookmarkData("activity-1", "other-user") - activityState.onBookmarkAdded(bookmark) + val bookmark = bookmarkData("activity-1", "other-user") + activityState.onBookmarkUpserted(bookmark) - val expectedActivity = initialActivity.copy(bookmarkCount = 1, ownBookmarks = emptyList()) - assertEquals(expectedActivity, activityState.activity.value) - } + val expectedActivity = bookmark.activity.copy(ownBookmarks = emptyList()) + assertEquals(expectedActivity, activityState.activity.value) + } @Test fun `on onPollClosed, then update poll`() = runTest { diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImplTest.kt index 9b3707488..0263e5ae0 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/BookmarkListStateImplTest.kt @@ -16,8 +16,6 @@ package io.getstream.feeds.android.client.internal.state import io.getstream.feeds.android.client.api.model.BookmarkData -import io.getstream.feeds.android.client.api.model.PaginationData -import io.getstream.feeds.android.client.api.model.PaginationResult import io.getstream.feeds.android.client.api.state.query.BookmarksQuery import io.getstream.feeds.android.client.api.state.query.BookmarksQueryConfig import io.getstream.feeds.android.client.api.state.query.BookmarksSort @@ -52,97 +50,88 @@ internal class BookmarkListStateImplTest { } @Test - fun `on bookmarkUpdated, then update specific bookmark`() = runTest { - val initialBookmarks = listOf(bookmarkData(), bookmarkData("bookmark-2", "user-2")) - val paginationResult = defaultPaginationResult(initialBookmarks) - bookmarkListState.onQueryMoreBookmarks(paginationResult, queryConfig) + fun `on onBookmarkUpserted, then add specific bookmark`() = runTest { + val initialBookmark = bookmarkData("activity-2", "user-2", createdAt = 2000) + setupInitialBookmarks(listOf(initialBookmark)) - val updatedBookmark = bookmarkData("activity-1", "user-1") - bookmarkListState.onBookmarkUpdated(updatedBookmark) + val updatedBookmark = bookmarkData("activity-1", "user-1", createdAt = 3000) + bookmarkListState.onBookmarkUpserted(updatedBookmark) - val updatedBookmarks = bookmarkListState.bookmarks.value - assertEquals(updatedBookmark, updatedBookmarks.find { it.id == updatedBookmark.id }) - assertEquals(initialBookmarks[1], updatedBookmarks.find { it.id == initialBookmarks[1].id }) + val expected = listOf(updatedBookmark, initialBookmark) + assertEquals(expected, bookmarkListState.bookmarks.value) + } + + @Test + fun `on onBookmarkUpserted, then update specific bookmark`() = runTest { + val bookmark1 = bookmarkData("activity-2", "user-2", createdAt = 2000) + val bookmark2 = bookmarkData("activity-1", "user-1", createdAt = 1000) + setupInitialBookmarks(listOf(bookmark1, bookmark2)) + + val updatedBookmark = bookmarkData("activity-1", "user-1", createdAt = 3000) + bookmarkListState.onBookmarkUpserted(updatedBookmark) + + val expected = listOf(updatedBookmark, bookmark1) + assertEquals(expected, bookmarkListState.bookmarks.value) } @Test fun `on bookmarkFolderUpdated, then update bookmarks with folder reference`() = runTest { - val folder = bookmarkFolderData() - val initialBookmarks = - listOf( - bookmarkData(folder = folder), - bookmarkData( - "activity-2", - "user-2", - folder = bookmarkFolderData("folder-2", "Folder 2"), - ), - ) - val paginationResult = defaultPaginationResult(initialBookmarks) - bookmarkListState.onQueryMoreBookmarks(paginationResult, queryConfig) + val folder = bookmarkFolderData("folder-1", "Folder 1") + val bookmark1 = bookmarkData(folder = folder) + val bookmark2 = + bookmarkData("activity-2", "user-2", bookmarkFolderData("folder-2", "Folder 2")) + setupInitialBookmarks(listOf(bookmark1, bookmark2)) val updatedFolder = bookmarkFolderData("folder-1", "Updated Folder") bookmarkListState.onBookmarkFolderUpdated(updatedFolder) - val updatedBookmarks = bookmarkListState.bookmarks.value - val bookmarkWithUpdatedFolder = updatedBookmarks.find { it.folder?.id == updatedFolder.id } - assertEquals(updatedFolder, bookmarkWithUpdatedFolder?.folder) + val expected = listOf(bookmark1.copy(folder = updatedFolder), bookmark2) + assertEquals(expected, bookmarkListState.bookmarks.value) } @Test fun `on bookmarkFolderRemoved, then remove folder reference from bookmarks`() = runTest { - val folder = bookmarkFolderData() - val initialBookmarks = - listOf( - bookmarkData(folder = folder), - bookmarkData( - "activity-2", - "user-2", - folder = bookmarkFolderData("folder-2", "Folder 2"), - ), - ) - val paginationResult = defaultPaginationResult(initialBookmarks) - bookmarkListState.onQueryMoreBookmarks(paginationResult, queryConfig) + val folder = bookmarkFolderData("folder-1", "Folder 1") + val bookmark1 = bookmarkData(folder = folder) + val bookmark2 = + bookmarkData("activity-2", "user-2", bookmarkFolderData("folder-2", "Folder 2")) + setupInitialBookmarks(listOf(bookmark1, bookmark2)) bookmarkListState.onBookmarkFolderRemoved(folder.id) - val updatedBookmarks = bookmarkListState.bookmarks.value - val bookmarkWithoutFolder = updatedBookmarks.find { it.id == initialBookmarks[0].id } - assertNull(bookmarkWithoutFolder?.folder) - assertEquals(initialBookmarks[1], updatedBookmarks.find { it.id == initialBookmarks[1].id }) + val expected = listOf(bookmark1.copy(folder = null), bookmark2) + assertEquals(expected, bookmarkListState.bookmarks.value) } @Test fun `on bookmarkRemoved, then remove specific bookmark`() = runTest { - val initialBookmarks = - listOf( - bookmarkData("activity-1", "user-1"), - bookmarkData("activity-2", "user-2"), - bookmarkData("activity-3", "user-3"), - ) - val paginationResult = - PaginationResult(models = initialBookmarks, pagination = PaginationData()) - bookmarkListState.onQueryMoreBookmarks(paginationResult, queryConfig) + val bookmark1 = bookmarkData("activity-1", "user-1") + val bookmark2 = bookmarkData("activity-2", "user-2") + val bookmark3 = bookmarkData("activity-3", "user-3") + setupInitialBookmarks(listOf(bookmark1, bookmark2, bookmark3)) - val bookmarkToRemove = initialBookmarks[1] - bookmarkListState.onBookmarkRemoved(bookmarkToRemove) + bookmarkListState.onBookmarkRemoved(bookmark2) - val remainingBookmarks = bookmarkListState.bookmarks.value - assertEquals(2, remainingBookmarks.size) - assertEquals(listOf(initialBookmarks[0], initialBookmarks[2]), remainingBookmarks) + val expected = listOf(bookmark1, bookmark3) + assertEquals(expected, bookmarkListState.bookmarks.value) } @Test fun `on bookmarkRemoved with nonexistent bookmark, then keep all bookmarks`() = runTest { - val initialBookmarks = - listOf(bookmarkData("activity-1", "user-1"), bookmarkData("activity-2", "user-2")) - val paginationResult = - PaginationResult(models = initialBookmarks, pagination = PaginationData()) - bookmarkListState.onQueryMoreBookmarks(paginationResult, queryConfig) + val bookmark1 = bookmarkData("activity-1", "user-1") + val bookmark2 = bookmarkData("activity-2", "user-2") + setupInitialBookmarks(listOf(bookmark1, bookmark2)) val nonexistentBookmark = bookmarkData("activity-999", "user-999") bookmarkListState.onBookmarkRemoved(nonexistentBookmark) - assertEquals(initialBookmarks, bookmarkListState.bookmarks.value) + val expected = listOf(bookmark1, bookmark2) + assertEquals(expected, bookmarkListState.bookmarks.value) + } + + private fun setupInitialBookmarks(bookmarks: List) { + val paginationResult = defaultPaginationResult(bookmarks) + bookmarkListState.onQueryMoreBookmarks(paginationResult, queryConfig) } companion object { 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 b558c5b98..2ac23bd21 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 @@ -282,11 +282,7 @@ internal class FeedImplTest { val result = feed.addBookmark(activityId, request) - val updated = - activity.copy( - ownBookmarks = listOf(bookmark), - bookmarkCount = activity.bookmarkCount + 1, - ) + val updated = bookmark.activity.copy(ownBookmarks = listOf(bookmark)) assertEquals(bookmark, result.getOrNull()) assertEquals(listOf(updated), feed.state.activities.value) verify { stateEventListener.onEvent(StateUpdateEvent.BookmarkAdded(bookmark)) } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImplTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImplTest.kt index 1971255b5..021f4a5c5 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImplTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/FeedStateImplTest.kt @@ -233,14 +233,14 @@ internal class FeedStateImplTest { } @Test - fun `on bookmarkAdded, then add bookmark to activity`() = runTest { + fun `on onBookmarkUpserted, then add bookmark to activity`() = runTest { setupInitialState(listOf(activityData("activity-1"))) val bookmark = bookmarkData("activity-1", currentUserId) - feedState.onBookmarkAdded(bookmark) + feedState.onBookmarkUpserted(bookmark) - val activityWithBookmark = feedState.activities.value.find { it.id == "activity-1" } - assertEquals(1, activityWithBookmark?.bookmarkCount) + val expected = bookmark.activity.copy(ownBookmarks = listOf(bookmark)) + assertEquals(listOf(expected), feedState.activities.value) } @Test @@ -248,7 +248,7 @@ internal class FeedStateImplTest { setupInitialState(listOf(activityData("activity-1"))) val bookmark = bookmarkData("activity-1", currentUserId) - feedState.onBookmarkAdded(bookmark) + feedState.onBookmarkUpserted(bookmark) feedState.onBookmarkRemoved(bookmark) val activityWithoutBookmark = feedState.activities.value.find { it.id == "activity-1" } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandlerTest.kt index 5a31539a3..f4e68bd99 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityEventHandlerTest.kt @@ -89,7 +89,24 @@ internal class ActivityEventHandlerTest { testEventHandling( matchingEvent = matchingEvent, nonMatchingEvent = nonMatchingEvent, - verifyBlock = { state.onBookmarkAdded(matchingBookmark) }, + verifyBlock = { state.onBookmarkUpserted(matchingBookmark) }, + ) + } + + @Test + fun `on BookmarkUpdated, then handle based on feed and activity match`() { + val matchingActivity = activityData(activityId).copy(feeds = listOf(fid.rawValue)) + val matchingBookmark = bookmarkData().copy(activity = matchingActivity) + val matchingEvent = StateUpdateEvent.BookmarkUpdated(matchingBookmark) + + val nonMatchingActivity = matchingActivity.copy(feeds = listOf(differentFid)) + val nonMatchingBookmark = matchingBookmark.copy(activity = nonMatchingActivity) + val nonMatchingEvent = StateUpdateEvent.BookmarkUpdated(nonMatchingBookmark) + + testEventHandling( + matchingEvent = matchingEvent, + nonMatchingEvent = nonMatchingEvent, + verifyBlock = { state.onBookmarkUpserted(matchingBookmark) }, ) } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandlerTest.kt index b740f59d8..b7d87d05b 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/ActivityListEventHandlerTest.kt @@ -60,13 +60,23 @@ internal class ActivityListEventHandlerTest { } @Test - fun `on BookmarkAdded, then call onBookmarkAdded`() { + fun `on BookmarkAdded, then call onBookmarkUpserted`() { val bookmark = bookmarkData() val event = StateUpdateEvent.BookmarkAdded(bookmark) handler.onEvent(event) - verify { state.onBookmarkAdded(bookmark) } + verify { state.onBookmarkUpserted(bookmark) } + } + + @Test + fun `on BookmarkUpdated, then call onBookmarkUpserted`() { + val bookmark = bookmarkData() + val event = StateUpdateEvent.BookmarkUpdated(bookmark) + + handler.onEvent(event) + + verify { state.onBookmarkUpserted(bookmark) } } @Test diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandlerTest.kt index ca859b21b..8cff8e57a 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/BookmarkListEventHandlerTest.kt @@ -17,66 +17,67 @@ package io.getstream.feeds.android.client.internal.state.event.handler import io.getstream.feeds.android.client.internal.state.BookmarkListStateUpdates import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkAdded +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkDeleted +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkFolderDeleted +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkFolderUpdated +import io.getstream.feeds.android.client.internal.state.event.StateUpdateEvent.BookmarkUpdated +import io.getstream.feeds.android.client.internal.subscribe.StateUpdateEventListener import io.getstream.feeds.android.client.internal.test.TestData.bookmarkData import io.getstream.feeds.android.client.internal.test.TestData.bookmarkFolderData import io.getstream.feeds.android.client.internal.test.TestData.commentData +import io.mockk.MockKVerificationScope import io.mockk.called import io.mockk.mockk -import io.mockk.verify -import org.junit.Test - -internal class BookmarkListEventHandlerTest { - - private val state: BookmarkListStateUpdates = mockk(relaxed = true) - private val handler = BookmarkListEventHandler(state) - - @Test - fun `on BookmarkFolderDeletedEvent, then call onBookmarkFolderRemoved`() { - val folderId = "folder-1" - val event = StateUpdateEvent.BookmarkFolderDeleted(folderId) - - handler.onEvent(event) - - verify { state.onBookmarkFolderRemoved(folderId) } - } - - @Test - fun `on BookmarkFolderUpdatedEvent, then call onBookmarkFolderUpdated`() { - val folder = bookmarkFolderData() - val event = StateUpdateEvent.BookmarkFolderUpdated(folder) - - handler.onEvent(event) - - verify { state.onBookmarkFolderUpdated(folder) } - } - - @Test - fun `on BookmarkUpdatedEvent, then call onBookmarkUpdated`() { - val bookmark = bookmarkData() - val event = StateUpdateEvent.BookmarkUpdated(bookmark) - - handler.onEvent(event) - - verify { state.onBookmarkUpdated(bookmark) } - } - - @Test - fun `on BookmarkDeletedEvent, then call onBookmarkRemoved`() { - val bookmark = bookmarkData() - val event = StateUpdateEvent.BookmarkDeleted(bookmark) - - handler.onEvent(event) - - verify { state.onBookmarkRemoved(bookmark) } - } - - @Test - fun `on unknown event, then do nothing`() { - val comment = commentData() - val unknownEvent = StateUpdateEvent.CommentAdded("feed-1", comment) - - handler.onEvent(unknownEvent) - - verify { state wasNot called } +import org.junit.runners.Parameterized + +internal class BookmarkListEventHandlerTest( + testName: String, + event: StateUpdateEvent, + verifyBlock: MockKVerificationScope.(BookmarkListStateUpdates) -> Unit, +) : BaseEventHandlerTest(testName, event, verifyBlock) { + + override val state: BookmarkListStateUpdates = mockk(relaxed = true) + override val handler: StateUpdateEventListener = BookmarkListEventHandler(state) + + companion object { + private val bookmark = bookmarkData() + private val folder = bookmarkFolderData() + + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): Collection> = + listOf( + testParams( + name = "BookmarkFolderDeleted", + event = BookmarkFolderDeleted("folder-1"), + verifyBlock = { state -> state.onBookmarkFolderRemoved("folder-1") }, + ), + testParams( + name = "BookmarkFolderUpdated", + event = BookmarkFolderUpdated(folder), + verifyBlock = { state -> state.onBookmarkFolderUpdated(folder) }, + ), + testParams( + name = "BookmarkAdded", + event = BookmarkAdded(bookmark), + verifyBlock = { state -> state.onBookmarkUpserted(bookmark) }, + ), + testParams( + name = "BookmarkDeleted", + event = BookmarkDeleted(bookmark), + verifyBlock = { state -> state.onBookmarkRemoved(bookmark) }, + ), + testParams( + name = "BookmarkUpdated", + event = BookmarkUpdated(bookmark), + verifyBlock = { state -> state.onBookmarkUpserted(bookmark) }, + ), + testParams( + name = "unknown event", + event = StateUpdateEvent.CommentAdded("feed-1", commentData()), + verifyBlock = { state -> state wasNot called }, + ), + ) } } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandlerTest.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandlerTest.kt index 023d3b556..bd4b47cb1 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandlerTest.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/state/event/handler/FeedEventHandlerTest.kt @@ -167,7 +167,24 @@ internal class FeedEventHandlerTest { testEventHandling( matchingEvent = matchingEvent, nonMatchingEvent = nonMatchingEvent, - verifyBlock = { state.onBookmarkAdded(matchingBookmark) }, + verifyBlock = { state.onBookmarkUpserted(matchingBookmark) }, + ) + } + + @Test + fun `on BookmarkUpdated, then handle based on activity feed match`() { + val matchingActivity = activityData().copy(feeds = listOf(fid.rawValue, "other:feed")) + val matchingBookmark = bookmarkData().copy(activity = matchingActivity) + val matchingEvent = StateUpdateEvent.BookmarkUpdated(matchingBookmark) + + val nonMatchingActivity = activityData().copy(feeds = listOf("other:feed", "another:feed")) + val nonMatchingBookmark = bookmarkData().copy(activity = nonMatchingActivity) + val nonMatchingEvent = StateUpdateEvent.BookmarkUpdated(nonMatchingBookmark) + + testEventHandling( + matchingEvent = matchingEvent, + nonMatchingEvent = nonMatchingEvent, + verifyBlock = { state.onBookmarkUpserted(matchingBookmark) }, ) } diff --git a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/test/TestData.kt b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/test/TestData.kt index a893815af..9580fa093 100644 --- a/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/test/TestData.kt +++ b/stream-feeds-android-client/src/test/kotlin/io/getstream/feeds/android/client/internal/test/TestData.kt @@ -260,10 +260,11 @@ internal object TestData { activityId: String = "activity-1", userId: String = "user-1", folder: BookmarkFolderData? = null, + createdAt: Long = 1000, ): BookmarkData = BookmarkData( activity = activityData(activityId), - createdAt = Date(1000), + createdAt = Date(createdAt), custom = emptyMap(), folder = folder, updatedAt = Date(1000),