Browse Source

Add some tests on RoomFlowNode

test/jme/compound-poc
ganfra 1 year ago
parent
commit
776e9bd221
  1. 3
      appnav/build.gradle.kts
  2. 5
      appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt
  3. 130
      appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt
  4. 3
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
  5. 2
      gradle/libs.versions.toml
  6. 27
      libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt
  7. 5
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt
  8. 2
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  9. 2
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
  10. 1
      services/appnavstate/impl/build.gradle.kts
  11. 28
      services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateServiceTest.kt
  12. 1
      services/appnavstate/test/build.gradle.kts
  13. 14
      services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt
  14. 48
      services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/NoopAppNavigationStateService.kt

3
appnav/build.gradle.kts

@ -66,4 +66,7 @@ dependencies { @@ -66,4 +66,7 @@ dependencies {
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.rageshake.impl)
testImplementation(projects.services.appnavstate.test)
testImplementation(libs.test.appyx.junit)
testImplementation(libs.test.arch.core)
}

5
appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt

@ -83,6 +83,7 @@ class RoomFlowNode @AssistedInject constructor( @@ -83,6 +83,7 @@ class RoomFlowNode @AssistedInject constructor(
Timber.v("OnCreate")
plugins<LifecycleCallback>().forEach { it.onFlowCreated(inputs.room) }
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
fetchRoomMembers()
},
onDestroy = {
Timber.v("OnDestroy")
@ -91,8 +92,6 @@ class RoomFlowNode @AssistedInject constructor( @@ -91,8 +92,6 @@ class RoomFlowNode @AssistedInject constructor(
appNavigationStateService.onLeavingRoom(id)
}
)
lifecycleScope.fetchRoomMembers()
roomMembershipObserver.updates
.filter { update -> update.roomId == inputs.room.roomId && !update.isUserInRoom }
.onEach {
@ -101,7 +100,7 @@ class RoomFlowNode @AssistedInject constructor( @@ -101,7 +100,7 @@ class RoomFlowNode @AssistedInject constructor(
.launchIn(lifecycleScope)
}
private fun CoroutineScope.fetchRoomMembers() = launch {
private fun fetchRoomMembers() = lifecycleScope.launch {
val room = inputs.room
room.fetchMembers()
.onFailure {

130
appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt

@ -0,0 +1,130 @@ @@ -0,0 +1,130 @@
/*
* 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 io.element.android.appnav
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Lifecycle
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.navmodel.backstack.activeElement
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.bumble.appyx.testing.unit.common.helper.nodeTestHelper
import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper
import com.google.common.truth.Truth
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
import io.element.android.libraries.architecture.childNode
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.services.appnavstate.test.NoopAppNavigationStateService
import org.junit.Rule
import org.junit.Test
class RoomFlowNodeTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private class FakeMessagesEntryPoint : MessagesEntryPoint {
var nodeId: String? = null
var callback: MessagesEntryPoint.Callback? = null
override fun createNode(parentNode: Node, buildContext: BuildContext, callback: MessagesEntryPoint.Callback): Node {
return node(buildContext) {}.also {
nodeId = it.id
this.callback = callback
}
}
}
private class FakeRoomDetailsEntryPoint : RoomDetailsEntryPoint {
var nodeId: String? = null
override fun createNode(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
return node(buildContext) {}.also {
nodeId = it.id
}
}
}
private fun aRoomFlowNode(
plugins: List<Plugin>,
messagesEntryPoint: MessagesEntryPoint = FakeMessagesEntryPoint(),
roomDetailsEntryPoint: RoomDetailsEntryPoint = FakeRoomDetailsEntryPoint(),
) = RoomFlowNode(
buildContext = BuildContext.root(savedStateMap = null),
plugins = plugins,
messagesEntryPoint = messagesEntryPoint,
roomDetailsEntryPoint = roomDetailsEntryPoint,
appNavigationStateService = NoopAppNavigationStateService(),
roomMembershipObserver = RoomMembershipObserver()
)
@Test
fun `given a room flow node when initialized then it fetches room members`() {
// GIVEN
val room = FakeMatrixRoom()
val inputs = RoomFlowNode.Inputs(room)
val roomFlowNode = aRoomFlowNode(listOf(inputs))
Truth.assertThat(room.areMembersFetched).isFalse()
// WHEN
roomFlowNode.nodeTestHelper()
// THEN
Truth.assertThat(room.areMembersFetched).isTrue()
}
@Test
fun `given a room flow node when initialized then it loads messages entry point`() {
// GIVEN
val room = FakeMatrixRoom()
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val inputs = RoomFlowNode.Inputs(room)
val roomFlowNode = aRoomFlowNode(listOf(inputs), fakeMessagesEntryPoint)
// WHEN
val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper()
// THEN
Truth.assertThat(roomFlowNode.backstack.activeElement).isEqualTo(RoomFlowNode.NavTarget.Messages)
roomFlowNodeTestHelper.assertChildHasLifecycle(RoomFlowNode.NavTarget.Messages, Lifecycle.State.CREATED)
val messagesNode = roomFlowNode.childNode(RoomFlowNode.NavTarget.Messages)!!
Truth.assertThat(messagesNode.id).isEqualTo(fakeMessagesEntryPoint.nodeId)
}
@Test
fun `given a room flow node when callback on room details is triggered then it loads room details entry point`() {
// GIVEN
val room = FakeMatrixRoom()
val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = RoomFlowNode.Inputs(room)
val roomFlowNode = aRoomFlowNode(listOf(inputs), fakeMessagesEntryPoint, fakeRoomDetailsEntryPoint)
val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper()
// WHEN
fakeMessagesEntryPoint.callback?.onRoomDetailsClicked()
// THEN
roomFlowNodeTestHelper.assertChildHasLifecycle(RoomFlowNode.NavTarget.RoomDetails, Lifecycle.State.CREATED)
val roomDetailsNode = roomFlowNode.childNode(RoomFlowNode.NavTarget.RoomDetails)!!
Truth.assertThat(roomDetailsNode.id).isEqualTo(fakeRoomDetailsEntryPoint.nodeId)
}
}

3
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt

@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState @@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -45,7 +44,7 @@ import org.junit.Test @@ -45,7 +44,7 @@ import org.junit.Test
@ExperimentalCoroutinesApi
class RoomDetailsPresenterTests {
private val roomMembershipObserver = RoomMembershipObserver(A_SESSION_ID)
private val roomMembershipObserver = RoomMembershipObserver()
@Test
fun `present - initial state is created from room info`() = runTest {

2
gradle/libs.versions.toml

@ -103,6 +103,7 @@ network_retrofit_converter_serialization = "com.jakewharton.retrofit:retrofit2-k @@ -103,6 +103,7 @@ network_retrofit_converter_serialization = "com.jakewharton.retrofit:retrofit2-k
# Test
test_core = { module = "androidx.test:core", version.ref = "test_core" }
test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" }
test_arch_core = "androidx.arch.core:core-testing:2.2.0"
test_junit = "junit:junit:4.13.2"
test_runner = "androidx.test:runner:1.5.2"
test_uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0"
@ -115,6 +116,7 @@ test_turbine = "app.cash.turbine:turbine:0.12.1" @@ -115,6 +116,7 @@ test_turbine = "app.cash.turbine:turbine:0.12.1"
test_truth = "com.google.truth:truth:1.1.3"
test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.11"
test_robolectric = "org.robolectric:robolectric:4.9.2"
test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" }
# Others
coil = { module = "io.coil-kt:coil", version.ref = "coil" }

27
libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* 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 io.element.android.libraries.architecture
import com.bumble.appyx.core.children.nodeOrNull
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
fun <NavTarget : Any> ParentNode<NavTarget>.childNode(navTarget: NavTarget): Node? {
val childMap = children.value
val key = childMap.keys.find { it.navTarget == navTarget }
return childMap[key]?.nodeOrNull
}

5
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt

@ -17,14 +17,11 @@ @@ -17,14 +17,11 @@
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class RoomMembershipObserver(
private val sessionId: SessionId,
) {
class RoomMembershipObserver {
data class RoomMembershipUpdate(
val roomId: RoomId,
val isUserInRoom: Boolean,

2
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

@ -142,7 +142,7 @@ class RustMatrixClient constructor( @@ -142,7 +142,7 @@ class RustMatrixClient constructor(
private val mediaResolver = RustMediaResolver(this)
private val isSyncing = AtomicBoolean(false)
private val roomMembershipObserver = RoomMembershipObserver(sessionId)
private val roomMembershipObserver = RoomMembershipObserver()
init {
client.setDelegate(clientDelegate)

2
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

@ -104,7 +104,7 @@ class FakeMatrixClient( @@ -104,7 +104,7 @@ class FakeMatrixClient(
override fun onSlidingSyncUpdate() {}
override fun roomMembershipObserver(): RoomMembershipObserver {
return RoomMembershipObserver(A_SESSION_ID)
return RoomMembershipObserver()
}
// Mocks

1
services/appnavstate/impl/build.gradle.kts

@ -47,4 +47,5 @@ dependencies { @@ -47,4 +47,5 @@ dependencies {
testImplementation(libs.coroutines.test)
testImplementation(libs.test.truth)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.services.appnavstate.test)
}

28
services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateServiceTest.kt

@ -24,37 +24,35 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -24,37 +24,35 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_SPACE_ID
import io.element.android.libraries.matrix.test.A_THREAD_ID
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.test.A_ROOM_OWNER
import io.element.android.services.appnavstate.test.A_SESSION_OWNER
import io.element.android.services.appnavstate.test.A_SPACE_OWNER
import io.element.android.services.appnavstate.test.A_THREAD_OWNER
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Test
private const val aSessionOwner = "aSessionOwner"
private const val aSpaceOwner = "aSpaceOwner"
private const val aRoomOwner = "aRoomOwner"
private const val aThreadOwner = "aThreadOwner"
class DefaultAppNavigationStateServiceTest {
@Test
fun testNavigation() = runTest {
val service = DefaultAppNavigationStateService()
service.onNavigateToSession(aSessionOwner, A_SESSION_ID)
service.onNavigateToSpace(aSpaceOwner, A_SPACE_ID)
service.onNavigateToRoom(aRoomOwner, A_ROOM_ID)
service.onNavigateToThread(aThreadOwner, A_THREAD_ID)
service.onNavigateToSession(A_SESSION_OWNER, A_SESSION_ID)
service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID)
service.onNavigateToRoom(A_ROOM_OWNER, A_ROOM_ID)
service.onNavigateToThread(A_THREAD_OWNER, A_THREAD_ID)
assertThat(service.appNavigationStateFlow.first()).isEqualTo(
AppNavigationState.Thread(
aThreadOwner, A_THREAD_ID,
A_THREAD_OWNER, A_THREAD_ID,
AppNavigationState.Room(
aRoomOwner,
A_ROOM_OWNER,
A_ROOM_ID,
AppNavigationState.Space(
aSpaceOwner,
A_SPACE_OWNER,
A_SPACE_ID,
AppNavigationState.Session(
aSessionOwner,
A_SESSION_OWNER,
A_SESSION_ID
)
)
@ -66,6 +64,6 @@ class DefaultAppNavigationStateServiceTest { @@ -66,6 +64,6 @@ class DefaultAppNavigationStateServiceTest {
@Test
fun testFailure() = runTest {
val service = DefaultAppNavigationStateService()
assertThrows(IllegalStateException::class.java) { service.onNavigateToSpace(aSpaceOwner, A_SPACE_ID) }
assertThrows(IllegalStateException::class.java) { service.onNavigateToSpace(A_SPACE_OWNER, A_SPACE_ID) }
}
}

1
services/appnavstate/test/build.gradle.kts

@ -27,4 +27,5 @@ android { @@ -27,4 +27,5 @@ android {
dependencies {
api(projects.libraries.matrix.api)
api(projects.services.appnavstate.api)
implementation(libs.coroutines.core)
}

14
services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt

@ -23,27 +23,31 @@ import io.element.android.libraries.matrix.api.core.SpaceId @@ -23,27 +23,31 @@ import io.element.android.libraries.matrix.api.core.SpaceId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.services.appnavstate.api.AppNavigationState
const val A_SESSION_OWNER = "aSessionOwner"
const val A_SPACE_OWNER = "aSpaceOwner"
const val A_ROOM_OWNER = "aRoomOwner"
const val A_THREAD_OWNER = "aThreadOwner"
fun anAppNavigationState(
sessionId: SessionId? = null,
spaceId: SpaceId? = MAIN_SPACE,
roomId: RoomId? = null,
threadId: ThreadId? = null,
owner: String = "a-owner",
): AppNavigationState {
if (sessionId == null) {
return AppNavigationState.Root
}
val session = AppNavigationState.Session(owner, sessionId)
val session = AppNavigationState.Session(A_SESSION_OWNER, sessionId)
if (spaceId == null) {
return session
}
val space = AppNavigationState.Space(owner, spaceId, session)
val space = AppNavigationState.Space(A_SPACE_OWNER, spaceId, session)
if (roomId == null) {
return space
}
val room = AppNavigationState.Room(owner, roomId, space)
val room = AppNavigationState.Room(A_ROOM_OWNER, roomId, space)
if (threadId == null) {
return room
}
return AppNavigationState.Thread(owner, threadId, room)
return AppNavigationState.Thread(A_THREAD_OWNER, threadId, room)
}

48
services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/NoopAppNavigationStateService.kt

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
/*
* 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 io.element.android.services.appnavstate.test
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.SpaceId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class NoopAppNavigationStateService : AppNavigationStateService {
private val currentAppNavigationState: MutableStateFlow<AppNavigationState> =
MutableStateFlow(AppNavigationState.Root)
override val appNavigationStateFlow: StateFlow<AppNavigationState> = currentAppNavigationState
override fun onNavigateToSession(owner: String, sessionId: SessionId) = Unit
override fun onLeavingSession(owner: String) = Unit
override fun onNavigateToSpace(owner: String, spaceId: SpaceId) = Unit
override fun onLeavingSpace(owner: String) = Unit
override fun onNavigateToRoom(owner: String, roomId: RoomId) = Unit
override fun onLeavingRoom(owner: String) = Unit
override fun onNavigateToThread(owner: String, threadId: ThreadId) = Unit
override fun onLeavingThread(owner: String) = Unit
}
Loading…
Cancel
Save