diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index a55900a5c4..18c8ea3bde 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -22,41 +22,33 @@ import com.airbnb.mvrx.Loading import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter +import im.vector.app.features.home.room.list.usecase.GetLatestPreviewableEventUseCase import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.voicebroadcast.isLive -import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent -import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo -import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class RoomSummaryItemFactory @Inject constructor( - private val sessionHolder: ActiveSessionHolder, private val displayableEventFormatter: DisplayableEventFormatter, private val dateFormatter: VectorDateFormatter, private val stringProvider: StringProvider, private val typingHelper: TypingHelper, private val avatarRenderer: AvatarRenderer, private val errorFormatter: ErrorFormatter, - private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, + private val getLatestPreviewableEventUseCase: GetLatestPreviewableEventUseCase, ) { fun create( @@ -142,7 +134,7 @@ class RoomSummaryItemFactory @Inject constructor( val showSelected = selectedRoomIds.contains(roomSummary.roomId) var latestFormattedEvent: CharSequence = "" var latestEventTime = "" - val latestEvent = roomSummary.getVectorLatestPreviewableEvent() + val latestEvent = getLatestPreviewableEventUseCase.execute(roomSummary.roomId) if (latestEvent != null) { latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not()) latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST) @@ -150,7 +142,8 @@ class RoomSummaryItemFactory @Inject constructor( val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers) // Skip typing while there is a live voice broadcast - .takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty() + .takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() } + .orEmpty() return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) { createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick) @@ -240,14 +233,4 @@ class RoomSummaryItemFactory @Inject constructor( else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1) } } - - private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? { - val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent - val liveVoiceBroadcastTimelineEvent = getRoomLiveVoiceBroadcastsUseCase.execute(roomId).lastOrNull() - ?.root?.eventId?.let { room.getTimelineEvent(it) } - return latestPreviewableEvent?.takeIf { it.root.getClearType() == EventType.CALL_INVITE } - ?: liveVoiceBroadcastTimelineEvent - ?: latestPreviewableEvent - ?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt new file mode 100644 index 0000000000..6a50e87562 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.usecase + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.voicebroadcast.isLive +import im.vector.app.features.voicebroadcast.isVoiceBroadcast +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase +import im.vector.app.features.voicebroadcast.voiceBroadcastId +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class GetLatestPreviewableEventUseCase @Inject constructor( + private val sessionHolder: ActiveSessionHolder, + private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, +) { + + fun execute(roomId: String): TimelineEvent? { + val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return null + val roomSummary = room.roomSummary() ?: return null + return getCallEvent(roomSummary) + ?: getLiveVoiceBroadcastEvent(room) + ?: getDefaultLatestEvent(room, roomSummary) + } + + private fun getCallEvent(roomSummary: RoomSummary): TimelineEvent? { + return roomSummary.latestPreviewableEvent + ?.takeIf { it.root.getClearType() == EventType.CALL_INVITE } + } + + private fun getLiveVoiceBroadcastEvent(room: Room): TimelineEvent? { + return getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId) + .lastOrNull() + ?.voiceBroadcastId + ?.let { room.getTimelineEvent(it) } + } + + private fun getDefaultLatestEvent(room: Room, roomSummary: RoomSummary): TimelineEvent? { + val latestPreviewableEvent = roomSummary.latestPreviewableEvent + + // If the default latest event is a live voice broadcast (paused or resumed), rely to the started event + val liveVoiceBroadcastEventId = latestPreviewableEvent?.root?.asVoiceBroadcastEvent()?.takeIf { it.isLive }?.voiceBroadcastId + if (liveVoiceBroadcastEventId != null) { + return room.getTimelineEvent(liveVoiceBroadcastEventId) + } + + return latestPreviewableEvent + ?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt index 9c3d1cced3..e821e09119 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt @@ -20,10 +20,12 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.voiceBroadcastId +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getTimelineEvent import timber.log.Timber import javax.inject.Inject @@ -47,8 +49,14 @@ class GetVoiceBroadcastStateEventUseCase @Inject constructor( * Get the most recent event related to the given voice broadcast. */ private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? { - return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) - .mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } } - .maxByOrNull { it.root.originServerTs ?: 0 } + val startedEvent = room.getTimelineEvent(voiceBroadcast.voiceBroadcastId) + return if (startedEvent?.root?.isRedacted().orTrue()) { + null + } else { + room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) + .mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent() } + .filterNot { it.root.isRedacted() } + .maxByOrNull { it.root.originServerTs ?: 0 } + } } } diff --git a/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt new file mode 100644 index 0000000000..5d526c783b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.usecase + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeRoom +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +private const val A_ROOM_ID = "a-room-id" + +internal class GetLatestPreviewableEventUseCaseTest { + + private val fakeRoom = FakeRoom() + private val fakeSessionHolder = FakeActiveSessionHolder() + private val fakeRoomSummary = mockk() + private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk() + + private val getLatestPreviewableEventUseCase = GetLatestPreviewableEventUseCase( + fakeSessionHolder.instance, + fakeGetRoomLiveVoiceBroadcastsUseCase, + ) + + @Before + fun setup() { + every { fakeSessionHolder.instance.getSafeActiveSession()?.getRoom(A_ROOM_ID) } returns fakeRoom + every { fakeRoom.roomSummary() } returns fakeRoomSummary + every { fakeRoom.roomId } returns A_ROOM_ID + every { fakeRoom.timelineService().getTimelineEvent(any()) } answers { + mockk(relaxed = true) { + every { eventId } returns firstArg() + } + } + } + + @Test + fun `given the latest event is a call invite and there is a live broadcast, when execute, returns the call event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.CALL_INVITE + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf( + givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "id1"), + givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "id1"), + ).mapNotNull { it.asVoiceBroadcastEvent() } + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result shouldBe aLatestPreviewableEvent + } + + @Test + fun `given the latest event is not a call invite and there is a live broadcast, when execute, returns the latest broadcast event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.MESSAGE + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf( + givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "vb_id1"), + givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "vb_id2"), + ).mapNotNull { it.asVoiceBroadcastEvent() } + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result?.eventId shouldBeEqualTo "vb_id2" + } + + @Test + fun `given there is no live broadcast, when execute, returns the latest event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.MESSAGE + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result shouldBe aLatestPreviewableEvent + } + + @Test + fun `given there is no live broadcast and the latest event is a vb message, when execute, returns null`() { + // Given + val aLatestPreviewableEvent = mockk { + every { root.type } returns EventType.MESSAGE + every { root.getClearType() } returns EventType.MESSAGE + every { root.getClearContent() } returns mapOf( + MessageContent.MSG_TYPE_JSON_KEY to "m.audio", + VOICE_BROADCAST_CHUNK_KEY to "1", + "body" to "", + ) + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result.shouldBeNull() + } + + @Test + fun `given the latest event is an ended vb, when execute, returns the stopped event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { eventId } returns "id1" + every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STOPPED, "vb_id1") + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result?.eventId shouldBeEqualTo "id1" + } + + @Test + fun `given the latest event is a resumed vb, when execute, returns the started event`() { + // Given + val aLatestPreviewableEvent = mockk { + every { eventId } returns "id1" + every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.RESUMED, "vb_id1") + } + every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent + every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList() + + // When + val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID) + + // Then + result?.eventId shouldBeEqualTo "vb_id1" + } + + private fun givenAVoiceBroadcastEvent( + eventId: String, + state: VoiceBroadcastState, + voiceBroadcastId: String, + ): Event = mockk { + every { this@mockk.eventId } returns eventId + every { getClearType() } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { content } returns mapOf( + "state" to state.value, + "m.relates_to" to mapOf( + "rel_type" to RelationType.REFERENCE, + "event_id" to voiceBroadcastId + ) + ) + } +} diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt index ea4777cb13..00b04aea81 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCaseTest.kt @@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.VoiceBroadcast +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeSession import io.mockk.every import io.mockk.mockk @@ -40,6 +41,7 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { fun `given there is no event related to the given vb, when execute, then return null`() { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(A_VOICE_BROADCAST_ID) } returns null every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns emptyList() // When @@ -54,9 +56,9 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) val aListOfTimelineEvents = listOf( - givenAVoiceBroadcastEvent(eventId = "event_id_1", isRedacted = false, timestamp = 1L), - givenAVoiceBroadcastEvent(eventId = "event_id_3", isRedacted = false, timestamp = 3L), - givenAVoiceBroadcastEvent(eventId = "event_id_2", isRedacted = false, timestamp = 2L), + givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.STOPPED, isRedacted = false, timestamp = 3L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L), ) every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents @@ -73,8 +75,8 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { // Given val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) val aListOfTimelineEvents = listOf( - givenAVoiceBroadcastEvent(eventId = "event_id_1", isRedacted = false, timestamp = 1L), - givenAVoiceBroadcastEvent(eventId = "event_id_2", isRedacted = true, timestamp = 2L), + givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.STOPPED, isRedacted = true, timestamp = 2L), ) every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents @@ -83,17 +85,41 @@ internal class GetVoiceBroadcastStateEventUseCaseTest { // Then result.shouldNotBeNull() - result.root.eventId shouldBeEqualTo "event_id_1" + result.root.eventId shouldBeEqualTo A_VOICE_BROADCAST_ID + } + + @Test + fun `given a not ended voice broadcast with a redacted start event, when execute, then return null`() { + // Given + val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID) + val aListOfTimelineEvents = listOf( + givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = true, timestamp = 1L), + givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L), + givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.RESUMED, isRedacted = false, timestamp = 3L), + ) + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents + + // When + val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast) + + // Then + result.shouldBeNull() } private fun givenAVoiceBroadcastEvent( eventId: String, + state: VoiceBroadcastState, isRedacted: Boolean, timestamp: Long, - ) = mockk(relaxed = true) { - every { root.eventId } returns eventId - every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO - every { root.isRedacted() } returns isRedacted - every { root.originServerTs } returns timestamp + ): TimelineEvent { + val timelineEvent = mockk { + every { root.eventId } returns eventId + every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + every { root.content } returns mapOf("state" to state.value) + every { root.isRedacted() } returns isRedacted + every { root.originServerTs } returns timestamp + } + every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(eventId) } returns timelineEvent + return timelineEvent } }